Index: projects/release-pkg/Makefile.inc1 =================================================================== --- projects/release-pkg/Makefile.inc1 (revision 297604) +++ projects/release-pkg/Makefile.inc1 (revision 297605) @@ -1,2466 +1,2466 @@ # # $FreeBSD$ # # Make command line options: # -DNO_CLEANDIR run ${MAKE} clean, instead of ${MAKE} cleandir # -DNO_CLEAN do not clean at all # -DDB_FROM_SRC use the user/group databases in src/etc instead of # the system database when installing. # -DNO_SHARE do not go into share subdir # -DKERNFAST define NO_KERNEL{CONFIG,CLEAN,OBJ} # -DNO_KERNELCONFIG do not run config in ${MAKE} buildkernel # -DNO_KERNELCLEAN do not run ${MAKE} clean in ${MAKE} buildkernel # -DNO_KERNELOBJ do not run ${MAKE} obj in ${MAKE} buildkernel # -DNO_PORTSUPDATE do not update ports in ${MAKE} update # -DNO_ROOT install without using root privilege # -DNO_DOCUPDATE do not update doc in ${MAKE} update # -DWITHOUT_CTF do not run the DTrace CTF conversion tools on built objects # LOCAL_DIRS="list of dirs" to add additional dirs to the SUBDIR list # LOCAL_ITOOLS="list of tools" to add additional tools to the ITOOLS list # LOCAL_LIB_DIRS="list of dirs" to add additional dirs to libraries target # LOCAL_MTREE="list of mtree files" to process to allow local directories # to be created before files are installed # LOCAL_TOOL_DIRS="list of dirs" to add additional dirs to the build-tools # list # METALOG="path to metadata log" to write permission and ownership # when NO_ROOT is set. (default: ${DESTDIR}/METALOG) # TARGET="machine" to crossbuild world for a different machine type # TARGET_ARCH= may be required when a TARGET supports multiple endians # BUILDENV_SHELL= shell to launch for the buildenv target (def:${SHELL}) # WORLD_FLAGS= additional flags to pass to make(1) during buildworld # KERNEL_FLAGS= additional flags to pass to make(1) during buildkernel # SUBDIR_OVERRIDE="list of dirs" to build rather than everything. # All libraries and includes, and some build tools will still build. # # The intended user-driven targets are: # buildworld - rebuild *everything*, including glue to help do upgrades # installworld- install everything built by "buildworld" # checkworld - run test suite on installed world # doxygen - build API documentation of the kernel # update - convenient way to update your source tree (eg: svn/svnup) # # Standard targets (not defined here) are documented in the makefiles in # /usr/share/mk. These include: # obj depend all install clean cleandepend cleanobj .if !defined(TARGET) || !defined(TARGET_ARCH) .error "Both TARGET and TARGET_ARCH must be defined." .endif LOCALBASE?= /usr/local # Cross toolchain changes must be in effect before bsd.compiler.mk # so that gets the right CC, and pass CROSS_TOOLCHAIN to submakes. .if defined(CROSS_TOOLCHAIN) .include "${LOCALBASE}/share/toolchains/${CROSS_TOOLCHAIN}.mk" CROSSENV+=CROSS_TOOLCHAIN="${CROSS_TOOLCHAIN}" .endif .include # don't depend on src.opts.mk doing it .include "share/mk/src.opts.mk" # We must do lib/ and libexec/ before bin/ in case of a mid-install error to # keep the users system reasonably usable. For static->dynamic root upgrades, # we don't want to install a dynamic binary without rtld and the needed # libraries. More commonly, for dynamic root, we don't want to install a # binary that requires a newer library version that hasn't been installed yet. # This ordering is not a guarantee though. The only guarantee of a working # system here would require fine-grained ordering of all components based # on their dependencies. SRCDIR?= ${.CURDIR} .if !empty(SUBDIR_OVERRIDE) SUBDIR= ${SUBDIR_OVERRIDE} .else SUBDIR= lib libexec .if !defined(NO_ROOT) && (make(installworld) || make(install)) # Ensure libraries are installed before progressing. SUBDIR+=.WAIT .endif SUBDIR+=bin .if ${MK_CDDL} != "no" SUBDIR+=cddl .endif SUBDIR+=gnu include .if ${MK_KERBEROS} != "no" SUBDIR+=kerberos5 .endif .if ${MK_RESCUE} != "no" SUBDIR+=rescue .endif SUBDIR+=sbin .if ${MK_CRYPT} != "no" SUBDIR+=secure .endif .if !defined(NO_SHARE) SUBDIR+=share .endif SUBDIR+=sys usr.bin usr.sbin .if ${MK_TESTS} != "no" SUBDIR+= tests .endif .if ${MK_OFED} != "no" SUBDIR+=contrib/ofed .endif # Local directories are last, since it is nice to at least get the base # system rebuilt before you do them. .for _DIR in ${LOCAL_DIRS} .if exists(${.CURDIR}/${_DIR}/Makefile) SUBDIR+= ${_DIR} .endif .endfor # Add LOCAL_LIB_DIRS, but only if they will not be picked up as a SUBDIR # of a LOCAL_DIRS directory. This allows LOCAL_DIRS=foo and # LOCAL_LIB_DIRS=foo/lib to behave as expected. .for _DIR in ${LOCAL_DIRS:M*/} ${LOCAL_DIRS:N*/:S|$|/|} _REDUNDENT_LIB_DIRS+= ${LOCAL_LIB_DIRS:M${_DIR}*} .endfor .for _DIR in ${LOCAL_LIB_DIRS} .if empty(_REDUNDENT_LIB_DIRS:M${_DIR}) && exists(${.CURDIR}/${_DIR}/Makefile) SUBDIR+= ${_DIR} .else .warning ${_DIR} not added to SUBDIR list. See UPDATING 20141121. .endif .endfor # We must do etc/ last as it hooks into building the man whatis file # by calling 'makedb' in share/man. This is only relevant for # install/distribute so they build the whatis file after every manpage is # installed. .if make(installworld) || make(install) SUBDIR+=.WAIT .endif SUBDIR+=etc .endif # !empty(SUBDIR_OVERRIDE) .if defined(NOCLEAN) .warning NOCLEAN option is deprecated. Use NO_CLEAN instead. NO_CLEAN= ${NOCLEAN} .endif .if defined(NO_CLEANDIR) CLEANDIR= clean cleandepend .else CLEANDIR= cleandir .endif LOCAL_TOOL_DIRS?= PACKAGEDIR?= ${DESTDIR}/${DISTDIR} .if empty(SHELL:M*csh*) BUILDENV_SHELL?=${SHELL} .else BUILDENV_SHELL?=/bin/sh .endif SVN?= /usr/local/bin/svn SVNFLAGS?= -r HEAD MAKEOBJDIRPREFIX?= /usr/obj .if !defined(OSRELDATE) .if exists(/usr/include/osreldate.h) OSRELDATE!= awk '/^\#define[[:space:]]*__FreeBSD_version/ { print $$3 }' \ /usr/include/osreldate.h .else OSRELDATE= 0 .endif .export OSRELDATE .endif # Set VERSION for CTFMERGE to use via the default CTFFLAGS=-L VERSION. .if !defined(VERSION) REVISION!= MK_AUTO_OBJ=no ${MAKE} -C ${SRCDIR}/release -V REVISION BRANCH!= MK_AUTO_OBJ=no ${MAKE} -C ${SRCDIR}/release -V BRANCH SRCRELDATE!= awk '/^\#define[[:space:]]*__FreeBSD_version/ { print $$3 }' \ ${SRCDIR}/sys/sys/param.h VERSION= FreeBSD ${REVISION}-${BRANCH:C/-p[0-9]+$//} ${TARGET_ARCH} ${SRCRELDATE} .export VERSION .endif .if !defined(PKG_VERSION) REVISION!= ${MAKE} -C ${SRCDIR}/release -V REVISION BRANCH!= ${MAKE} -C ${SRCDIR}/release -V BRANCH SRCRELDATE!= awk '/^\#define[[:space:]]*__FreeBSD_version/ { print $$3 }' \ ${SRCDIR}/sys/sys/param.h .if ${BRANCH:MSTABLE*} || ${BRANCH:MCURRENT*} TIMENOW= %Y%m%d%H%M%S EXTRA_REVISION= .s${TIMENOW:gmtime} .endif .if ${BRANCH:M*-p*} EXTRA_REVISION= _${BRANCH:C/.*-p([0-9]+$)/\1/} .endif PKG_VERSION= ${REVISION}${EXTRA_REVISION} .endif KNOWN_ARCHES?= aarch64/arm64 \ amd64 \ arm \ armeb/arm \ armv6/arm \ armv6hf/arm \ i386 \ i386/pc98 \ mips \ mipsel/mips \ mips64el/mips \ mips64/mips \ mipsn32el/mips \ mipsn32/mips \ powerpc \ powerpc64/powerpc \ riscv64/riscv \ sparc64 .if ${TARGET} == ${TARGET_ARCH} _t= ${TARGET} .else _t= ${TARGET_ARCH}/${TARGET} .endif .for _t in ${_t} .if empty(KNOWN_ARCHES:M${_t}) .error Unknown target ${TARGET_ARCH}:${TARGET}. .endif .endfor .if ${TARGET} == ${MACHINE} TARGET_CPUTYPE?=${CPUTYPE} .else TARGET_CPUTYPE?= .endif .if !empty(TARGET_CPUTYPE) _TARGET_CPUTYPE=${TARGET_CPUTYPE} .else _TARGET_CPUTYPE=dummy .endif _CPUTYPE!= MK_AUTO_OBJ=no MAKEFLAGS= CPUTYPE=${_TARGET_CPUTYPE} ${MAKE} \ -f /dev/null -m ${.CURDIR}/share/mk -V CPUTYPE .if ${_CPUTYPE} != ${_TARGET_CPUTYPE} .error CPUTYPE global should be set with ?=. .endif .if make(buildworld) BUILD_ARCH!= uname -p .if ${MACHINE_ARCH} != ${BUILD_ARCH} .error To cross-build, set TARGET_ARCH. .endif .endif .if ${MACHINE} == ${TARGET} && ${MACHINE_ARCH} == ${TARGET_ARCH} && !defined(CROSS_BUILD_TESTING) OBJTREE= ${MAKEOBJDIRPREFIX} .else OBJTREE= ${MAKEOBJDIRPREFIX}/${TARGET}.${TARGET_ARCH} .endif WORLDTMP= ${OBJTREE}${.CURDIR}/tmp BPATH= ${WORLDTMP}/legacy/usr/sbin:${WORLDTMP}/legacy/usr/bin:${WORLDTMP}/legacy/bin XPATH= ${WORLDTMP}/usr/sbin:${WORLDTMP}/usr/bin STRICTTMPPATH= ${BPATH}:${XPATH} TMPPATH= ${STRICTTMPPATH}:${PATH} # # Avoid running mktemp(1) unless actually needed. # It may not be functional, e.g., due to new ABI # when in the middle of installing over this system. # .if make(distributeworld) || make(installworld) || make(stageworld) INSTALLTMP!= /usr/bin/mktemp -d -u -t install .endif .if make(stagekernel) || make(distributekernel) TAGS+= kernel PACKAGE= kernel .endif # # Building a world goes through the following stages # # 1. legacy stage [BMAKE] # This stage is responsible for creating compatibility # shims that are needed by the bootstrap-tools, # build-tools and cross-tools stages. These are generally # APIs that tools from one of those three stages need to # build that aren't present on the host. # 1. bootstrap-tools stage [BMAKE] # This stage is responsible for creating programs that # are needed for backward compatibility reasons. They # are not built as cross-tools. # 2. build-tools stage [TMAKE] # This stage is responsible for creating the object # tree and building any tools that are needed during # the build process. Some programs are listed during # this phase because they build binaries to generate # files needed to build these programs. This stage also # builds the 'build-tools' target rather than 'all'. # 3. cross-tools stage [XMAKE] # This stage is responsible for creating any tools that # are needed for building the system. A cross-compiler is one # of them. This differs from build tools in two ways: # 1. the 'all' target is built rather than 'build-tools' # 2. these tools are installed into TMPPATH for stage 4. # 4. world stage [WMAKE] # This stage actually builds the world. # 5. install stage (optional) [IMAKE] # This stage installs a previously built world. # BOOTSTRAPPING?= 0 # Common environment for world related stages CROSSENV+= MAKEOBJDIRPREFIX=${OBJTREE} \ MACHINE_ARCH=${TARGET_ARCH} \ MACHINE=${TARGET} \ CPUTYPE=${TARGET_CPUTYPE} .if ${MK_GROFF} != "no" CROSSENV+= GROFF_BIN_PATH=${WORLDTMP}/legacy/usr/bin \ GROFF_FONT_PATH=${WORLDTMP}/legacy/usr/share/groff_font \ GROFF_TMAC_PATH=${WORLDTMP}/legacy/usr/share/tmac .endif .if defined(TARGET_CFLAGS) CROSSENV+= ${TARGET_CFLAGS} .endif # bootstrap-tools stage BMAKEENV= INSTALL="sh ${.CURDIR}/tools/install.sh" \ PATH=${BPATH}:${PATH} \ WORLDTMP=${WORLDTMP} \ MAKEFLAGS="-m ${.CURDIR}/tools/build/mk ${.MAKEFLAGS}" # need to keep this in sync with targets/pseudo/bootstrap-tools/Makefile BSARGS= DESTDIR= \ BOOTSTRAPPING=${OSRELDATE} \ SSP_CFLAGS= \ MK_HTML=no NO_LINT=yes MK_MAN=no \ -DNO_PIC MK_PROFILE=no -DNO_SHARED \ -DNO_CPU_CFLAGS MK_WARNS=no MK_CTF=no \ MK_CLANG_EXTRAS=no MK_CLANG_FULL=no \ MK_LLDB=no MK_TESTS=no \ MK_INCLUDES=yes BMAKE= MAKEOBJDIRPREFIX=${WORLDTMP} \ ${BMAKEENV} ${MAKE} ${WORLD_FLAGS} -f Makefile.inc1 \ ${BSARGS} # build-tools stage TMAKE= MAKEOBJDIRPREFIX=${OBJTREE} \ ${BMAKEENV} ${MAKE} ${WORLD_FLAGS} -f Makefile.inc1 \ TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH} \ DESTDIR= \ BOOTSTRAPPING=${OSRELDATE} \ SSP_CFLAGS= \ -DNO_LINT \ -DNO_CPU_CFLAGS MK_WARNS=no MK_CTF=no \ MK_CLANG_EXTRAS=no MK_CLANG_FULL=no \ MK_LLDB=no MK_TESTS=no # cross-tools stage XMAKE= TOOLS_PREFIX=${WORLDTMP} ${BMAKE} \ TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH} \ MK_GDB=no MK_TESTS=no # kernel-tools stage KTMAKEENV= INSTALL="sh ${.CURDIR}/tools/install.sh" \ PATH=${BPATH}:${PATH} \ WORLDTMP=${WORLDTMP} KTMAKE= TOOLS_PREFIX=${WORLDTMP} MAKEOBJDIRPREFIX=${WORLDTMP} \ ${KTMAKEENV} ${MAKE} ${WORLD_FLAGS} -f Makefile.inc1 \ DESTDIR= \ BOOTSTRAPPING=${OSRELDATE} \ SSP_CFLAGS= \ MK_HTML=no -DNO_LINT MK_MAN=no \ -DNO_PIC MK_PROFILE=no -DNO_SHARED \ -DNO_CPU_CFLAGS MK_WARNS=no MK_CTF=no # world stage WMAKEENV= ${CROSSENV} \ INSTALL="sh ${.CURDIR}/tools/install.sh" \ PATH=${TMPPATH} # make hierarchy HMAKE= PATH=${TMPPATH} ${MAKE} LOCAL_MTREE=${LOCAL_MTREE:Q} .if defined(NO_ROOT) HMAKE+= PATH=${TMPPATH} METALOG=${METALOG} -DNO_ROOT .endif .if defined(CROSS_TOOLCHAIN_PREFIX) CROSS_COMPILER_PREFIX?=${CROSS_TOOLCHAIN_PREFIX} CROSS_BINUTILS_PREFIX?=${CROSS_TOOLCHAIN_PREFIX} .endif # If we do not have a bootstrap binutils (because the in-tree one does not # support the target architecture), provide a default cross-binutils prefix. # This allows aarch64 builds, for example, to automatically use the # aarch64-binutils port or package. .if !make(showconfig) .if !empty(BROKEN_OPTIONS:MBINUTILS_BOOTSTRAP) && \ !defined(CROSS_BINUTILS_PREFIX) CROSS_BINUTILS_PREFIX=/usr/local/${TARGET_ARCH}-freebsd/bin/ .if !exists(${CROSS_BINUTILS_PREFIX}) .error In-tree binutils does not support the ${TARGET_ARCH} architecture. Install the ${TARGET_ARCH}-binutils port or package or set CROSS_BINUTILS_PREFIX. .endif .endif .endif XCOMPILERS= CC CXX CPP .for COMPILER in ${XCOMPILERS} .if defined(CROSS_COMPILER_PREFIX) X${COMPILER}?= ${CROSS_COMPILER_PREFIX}${${COMPILER}} .else X${COMPILER}?= ${${COMPILER}} .endif .endfor XBINUTILS= AS AR LD NM OBJCOPY OBJDUMP RANLIB SIZE STRINGS .for BINUTIL in ${XBINUTILS} .if defined(CROSS_BINUTILS_PREFIX) && \ exists(${CROSS_BINUTILS_PREFIX}${${BINUTIL}}) X${BINUTIL}?= ${CROSS_BINUTILS_PREFIX}${${BINUTIL}} .else X${BINUTIL}?= ${${BINUTIL}} .endif .endfor CROSSENV+= CC="${XCC} ${XCFLAGS}" CXX="${XCXX} ${XCFLAGS} ${XCXXFLAGS}" \ CPP="${XCPP} ${XCFLAGS}" \ AS="${XAS}" AR="${XAR}" LD="${XLD}" NM=${XNM} \ OBJDUMP=${XOBJDUMP} OBJCOPY="${XOBJCOPY}" \ RANLIB=${XRANLIB} STRINGS=${XSTRINGS} \ SIZE="${XSIZE}" .if defined(CROSS_BINUTILS_PREFIX) && exists(${CROSS_BINUTILS_PREFIX}) # In the case of xdev-build tools, CROSS_BINUTILS_PREFIX won't be a # directory, but the compiler will look in the right place for its # tools so we don't need to tell it where to look. BFLAGS+= -B${CROSS_BINUTILS_PREFIX} .endif # External compiler needs sysroot and target flags. .if ${XCC:N${CCACHE_BIN}:M/*} || ${MK_CROSS_COMPILER} == "no" .if !defined(CROSS_BINUTILS_PREFIX) || !exists(${CROSS_BINUTILS_PREFIX}) BFLAGS+= -B${WORLDTMP}/usr/bin .endif .if ${TARGET} == "arm" .if ${TARGET_ARCH:M*hf*} != "" TARGET_ABI= gnueabihf .else TARGET_ABI= gnueabi .endif .endif .if defined(X_COMPILER_TYPE) && ${X_COMPILER_TYPE} == gcc # GCC requires -isystem and -L when using a cross-compiler. XCFLAGS+= -isystem ${WORLDTMP}/usr/include -L${WORLDTMP}/usr/lib # Force using libc++ for external GCC. XCXXFLAGS+= -isystem ${WORLDTMP}/usr/include/c++/v1 -std=c++11 \ -nostdinc++ -L${WORLDTMP}/../lib/libc++ .else TARGET_ABI?= unknown TARGET_TRIPLE?= ${TARGET_ARCH:C/amd64/x86_64/}-${TARGET_ABI}-freebsd11.0 XCFLAGS+= -target ${TARGET_TRIPLE} .endif XCFLAGS+= --sysroot=${WORLDTMP} .else .endif # ${XCC:M/*} || ${MK_CROSS_COMPILER} == "no" .if !empty(BFLAGS) XCFLAGS+= ${BFLAGS} .endif .if ${MK_LIB32} != "no" && (${TARGET_ARCH} == "amd64" || \ ${TARGET_ARCH} == "powerpc64") LIBCOMPAT= 32 .include "Makefile.libcompat" .elif ${MK_LIBSOFT} != "no" && ${TARGET_ARCH} == "armv6" LIBCOMPAT= SOFT .include "Makefile.libcompat" .endif WMAKE= ${WMAKEENV} ${MAKE} ${WORLD_FLAGS} -f Makefile.inc1 DESTDIR=${WORLDTMP} -IMAKEENV= ${CROSSENV:N_LDSCRIPTROOT=*} +IMAKEENV= ${CROSSENV} IMAKE= ${IMAKEENV} ${MAKE} -f Makefile.inc1 \ ${IMAKE_INSTALL} ${IMAKE_MTREE} .if empty(.MAKEFLAGS:M-n) IMAKEENV+= PATH=${STRICTTMPPATH}:${INSTALLTMP} \ LD_LIBRARY_PATH=${INSTALLTMP} \ PATH_LOCALE=${INSTALLTMP}/locale IMAKE+= __MAKE_SHELL=${INSTALLTMP}/sh .else IMAKEENV+= PATH=${TMPPATH}:${INSTALLTMP} .endif .if defined(DB_FROM_SRC) INSTALLFLAGS+= -N ${.CURDIR}/etc MTREEFLAGS+= -N ${.CURDIR}/etc .endif _INSTALL_DDIR= ${DESTDIR}/${DISTDIR} INSTALL_DDIR= ${_INSTALL_DDIR:S://:/:g:C:/$::} .if defined(NO_ROOT) METALOG?= ${DESTDIR}/${DISTDIR}/METALOG IMAKE+= -DNO_ROOT METALOG=${METALOG} INSTALLFLAGS+= -U -M ${METALOG} -D ${INSTALL_DDIR} MTREEFLAGS+= -W .endif .if defined(BUILD_PKGS) INSTALLFLAGS+= -h sha256 .endif .if defined(DB_FROM_SRC) || defined(NO_ROOT) IMAKE_INSTALL= INSTALL="install ${INSTALLFLAGS}" IMAKE_MTREE= MTREE_CMD="mtree ${MTREEFLAGS}" .endif # kernel stage KMAKEENV= ${WMAKEENV} KMAKE= ${KMAKEENV} ${MAKE} ${.MAKEFLAGS} ${KERNEL_FLAGS} KERNEL=${INSTKERNNAME} # # buildworld # # Attempt to rebuild the entire system, with reasonable chance of # success, regardless of how old your existing system is. # _worldtmp: .PHONY .if ${.CURDIR:C/[^,]//g} != "" # The m4 build of sendmail files doesn't like it if ',' is used # anywhere in the path of it's files. @echo @echo "*** Error: path to source tree contains a comma ','" @echo false .endif @echo @echo "--------------------------------------------------------------" @echo ">>> Rebuilding the temporary build tree" @echo "--------------------------------------------------------------" .if !defined(NO_CLEAN) rm -rf ${WORLDTMP} .if defined(LIBCOMPAT) rm -rf ${LIBCOMPATTMP} .endif .else rm -rf ${WORLDTMP}/legacy/usr/include # XXX - These can depend on any header file. rm -f ${OBJTREE}${.CURDIR}/lib/libsysdecode/ioctl.c rm -f ${OBJTREE}${.CURDIR}/usr.bin/kdump/kdump_subr.c .endif .for _dir in \ lib lib/casper usr legacy/bin legacy/usr mkdir -p ${WORLDTMP}/${_dir} .endfor mtree -deU -f ${.CURDIR}/etc/mtree/BSD.usr.dist \ -p ${WORLDTMP}/legacy/usr >/dev/null .if ${MK_GROFF} != "no" mtree -deU -f ${.CURDIR}/etc/mtree/BSD.groff.dist \ -p ${WORLDTMP}/legacy/usr >/dev/null .endif mtree -deU -f ${.CURDIR}/etc/mtree/BSD.usr.dist \ -p ${WORLDTMP}/usr >/dev/null mtree -deU -f ${.CURDIR}/etc/mtree/BSD.include.dist \ -p ${WORLDTMP}/usr/include >/dev/null ln -sf ${.CURDIR}/sys ${WORLDTMP} .if ${MK_DEBUG_FILES} != "no" # We could instead disable debug files for these build stages mtree -deU -f ${.CURDIR}/etc/mtree/BSD.debug.dist \ -p ${WORLDTMP}/legacy/usr/lib >/dev/null mtree -deU -f ${.CURDIR}/etc/mtree/BSD.debug.dist \ -p ${WORLDTMP}/usr/lib >/dev/null .endif .if defined(LIBCOMPAT) mtree -deU -f ${.CURDIR}/etc/mtree/BSD.lib${libcompat}.dist \ -p ${WORLDTMP}/usr >/dev/null .if ${MK_DEBUG_FILES} != "no" mtree -deU -f ${.CURDIR}/etc/mtree/BSD.lib${libcompat}.dist \ -p ${WORLDTMP}/legacy/usr/lib/debug/usr >/dev/null mtree -deU -f ${.CURDIR}/etc/mtree/BSD.lib${libcompat}.dist \ -p ${WORLDTMP}/usr/lib/debug/usr >/dev/null .endif .endif .if ${MK_TESTS} != "no" mkdir -p ${WORLDTMP}${TESTSBASE} mtree -deU -f ${.CURDIR}/etc/mtree/BSD.tests.dist \ -p ${WORLDTMP}${TESTSBASE} >/dev/null .if ${MK_DEBUG_FILES} != "no" mkdir -p ${WORLDTMP}/usr/lib/debug/${TESTSBASE} mtree -deU -f ${.CURDIR}/etc/mtree/BSD.tests.dist \ -p ${WORLDTMP}/usr/lib/debug/${TESTSBASE} >/dev/null .endif .endif .for _mtree in ${LOCAL_MTREE} mtree -deU -f ${.CURDIR}/${_mtree} -p ${WORLDTMP} > /dev/null .endfor _legacy: @echo @echo "--------------------------------------------------------------" @echo ">>> stage 1.1: legacy release compatibility shims" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; ${BMAKE} legacy _bootstrap-tools: @echo @echo "--------------------------------------------------------------" @echo ">>> stage 1.2: bootstrap tools" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; ${BMAKE} bootstrap-tools _cleanobj: .if !defined(NO_CLEAN) @echo @echo "--------------------------------------------------------------" @echo ">>> stage 2.1: cleaning up the object tree" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; ${WMAKE} ${CLEANDIR} .if defined(LIBCOMPAT) ${_+_}cd ${.CURDIR}; ${LIBCOMPATWMAKE} -f Makefile.inc1 ${CLEANDIR} .endif .endif _obj: @echo @echo "--------------------------------------------------------------" @echo ">>> stage 2.2: rebuilding the object tree" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; ${WMAKE} obj _build-tools: @echo @echo "--------------------------------------------------------------" @echo ">>> stage 2.3: build tools" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; ${TMAKE} build-tools _cross-tools: @echo @echo "--------------------------------------------------------------" @echo ">>> stage 3: cross tools" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; ${XMAKE} cross-tools ${_+_}cd ${.CURDIR}; ${XMAKE} kernel-tools _includes: @echo @echo "--------------------------------------------------------------" @echo ">>> stage 4.1: building includes" @echo "--------------------------------------------------------------" # Special handling for SUBDIR_OVERRIDE in buildworld as they most likely need # headers from default SUBDIR. Do SUBDIR_OVERRIDE includes last. ${_+_}cd ${.CURDIR}; ${WMAKE} SUBDIR_OVERRIDE= SHARED=symlinks \ MK_INCLUDES=yes includes .if !empty(SUBDIR_OVERRIDE) && make(buildworld) ${_+_}cd ${.CURDIR}; ${WMAKE} MK_INCLUDES=yes SHARED=symlinks includes .endif _libraries: @echo @echo "--------------------------------------------------------------" @echo ">>> stage 4.2: building libraries" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; \ ${WMAKE} -DNO_FSCHG MK_HTML=no -DNO_LINT MK_MAN=no \ MK_PROFILE=no MK_TESTS=no MK_TESTS_SUPPORT=${MK_TESTS} libraries everything: @echo @echo "--------------------------------------------------------------" @echo ">>> stage 4.3: building everything" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; _PARALLEL_SUBDIR_OK=1 ${WMAKE} all WMAKE_TGTS= WMAKE_TGTS+= _worldtmp _legacy .if empty(SUBDIR_OVERRIDE) WMAKE_TGTS+= _bootstrap-tools .endif WMAKE_TGTS+= _cleanobj _obj _build-tools _cross-tools WMAKE_TGTS+= _includes _libraries WMAKE_TGTS+= everything .if defined(LIBCOMPAT) && empty(SUBDIR_OVERRIDE) WMAKE_TGTS+= build${libcompat} .endif buildworld: buildworld_prologue ${WMAKE_TGTS} buildworld_epilogue .ORDER: buildworld_prologue ${WMAKE_TGTS} buildworld_epilogue buildworld_prologue: @echo "--------------------------------------------------------------" @echo ">>> World build started on `LC_ALL=C date`" @echo "--------------------------------------------------------------" buildworld_epilogue: @echo @echo "--------------------------------------------------------------" @echo ">>> World build completed on `LC_ALL=C date`" @echo "--------------------------------------------------------------" # # We need to have this as a target because the indirection between Makefile # and Makefile.inc1 causes the correct PATH to be used, rather than a # modification of the current environment's PATH. In addition, we need # to quote multiword values. # buildenvvars: .PHONY @echo ${WMAKEENV:Q} ${.MAKE.EXPORTED:@v@$v=\"${$v}\"@} .if ${.TARGETS:Mbuildenv} .if ${.MAKEFLAGS:M-j} .error The buildenv target is incompatible with -j .endif .endif BUILDENV_DIR?= ${.CURDIR} buildenv: .PHONY @echo Entering world for ${TARGET_ARCH}:${TARGET} .if ${BUILDENV_SHELL:M*zsh*} @echo For ZSH you must run: export CPUTYPE=${TARGET_CPUTYPE} .endif @cd ${BUILDENV_DIR} && env ${WMAKEENV} BUILDENV=1 ${BUILDENV_SHELL} \ || true TOOLCHAIN_TGTS= ${WMAKE_TGTS:Neverything:Nbuild${libcompat}} toolchain: ${TOOLCHAIN_TGTS} kernel-toolchain: ${TOOLCHAIN_TGTS:N_includes:N_libraries} # # installcheck # # Checks to be sure system is ready for installworld/installkernel. # installcheck: _installcheck_world _installcheck_kernel _installcheck_world: _installcheck_kernel: # # Require DESTDIR to be set if installing for a different architecture or # using the user/group database in the source tree. # .if ${TARGET_ARCH} != ${MACHINE_ARCH} || ${TARGET} != ${MACHINE} || \ defined(DB_FROM_SRC) .if !make(distributeworld) _installcheck_world: __installcheck_DESTDIR _installcheck_kernel: __installcheck_DESTDIR __installcheck_DESTDIR: .if !defined(DESTDIR) || empty(DESTDIR) @echo "ERROR: Please set DESTDIR!"; \ false .endif .endif .endif .if !defined(DB_FROM_SRC) # # Check for missing UIDs/GIDs. # CHECK_UIDS= auditdistd CHECK_GIDS= audit .if ${MK_SENDMAIL} != "no" CHECK_UIDS+= smmsp CHECK_GIDS+= smmsp .endif .if ${MK_PF} != "no" CHECK_UIDS+= proxy CHECK_GIDS+= proxy authpf .endif .if ${MK_UNBOUND} != "no" CHECK_UIDS+= unbound CHECK_GIDS+= unbound .endif _installcheck_world: __installcheck_UGID __installcheck_UGID: .for uid in ${CHECK_UIDS} @if ! `id -u ${uid} >/dev/null 2>&1`; then \ echo "ERROR: Required ${uid} user is missing, see /usr/src/UPDATING."; \ false; \ fi .endfor .for gid in ${CHECK_GIDS} @if ! `find / -prune -group ${gid} >/dev/null 2>&1`; then \ echo "ERROR: Required ${gid} group is missing, see /usr/src/UPDATING."; \ false; \ fi .endfor .endif # # Required install tools to be saved in a scratch dir for safety. # .if ${MK_ZONEINFO} != "no" _zoneinfo= zic tzsetup .endif ITOOLS= [ awk cap_mkdb cat chflags chmod chown cmp cp \ date echo egrep find grep id install ${_install-info} \ ln make mkdir mtree mv pwd_mkdb \ rm sed services_mkdb sh strip sysctl test true uname wc ${_zoneinfo} \ ${LOCAL_ITOOLS} # Needed for share/man .if ${MK_MAN} != "no" ITOOLS+=makewhatis .endif # # distributeworld # # Distributes everything compiled by a `buildworld'. # # installworld # # Installs everything compiled by a 'buildworld'. # # Non-base distributions produced by the base system EXTRA_DISTRIBUTIONS= doc .if defined(LIBCOMPAT) EXTRA_DISTRIBUTIONS+= lib${libcompat} .endif .if ${MK_TESTS} != "no" EXTRA_DISTRIBUTIONS+= tests .endif DEBUG_DISTRIBUTIONS= .if ${MK_DEBUG_FILES} != "no" DEBUG_DISTRIBUTIONS+= base ${EXTRA_DISTRIBUTIONS:S,doc,,:S,tests,,} .endif MTREE_MAGIC?= mtree 2.0 distributeworld installworld stageworld: _installcheck_world mkdir -p ${INSTALLTMP} progs=$$(for prog in ${ITOOLS}; do \ if progpath=`which $$prog`; then \ echo $$progpath; \ else \ echo "Required tool $$prog not found in PATH." >&2; \ exit 1; \ fi; \ done); \ libs=$$(ldd -f "%o %p\n" -f "%o %p\n" $$progs 2>/dev/null | sort -u | \ while read line; do \ set -- $$line; \ if [ "$$2 $$3" != "not found" ]; then \ echo $$2; \ else \ echo "Required library $$1 not found." >&2; \ exit 1; \ fi; \ done); \ cp $$libs $$progs ${INSTALLTMP} cp -R $${PATH_LOCALE:-"/usr/share/locale"} ${INSTALLTMP}/locale .if defined(NO_ROOT) -mkdir -p ${METALOG:H} echo "#${MTREE_MAGIC}" > ${METALOG} .endif .if make(distributeworld) .for dist in ${EXTRA_DISTRIBUTIONS} -mkdir ${DESTDIR}/${DISTDIR}/${dist} mtree -deU -f ${.CURDIR}/etc/mtree/BSD.root.dist \ -p ${DESTDIR}/${DISTDIR}/${dist} >/dev/null mtree -deU -f ${.CURDIR}/etc/mtree/BSD.usr.dist \ -p ${DESTDIR}/${DISTDIR}/${dist}/usr >/dev/null mtree -deU -f ${.CURDIR}/etc/mtree/BSD.include.dist \ -p ${DESTDIR}/${DISTDIR}/${dist}/usr/include >/dev/null .if ${MK_DEBUG_FILES} != "no" mtree -deU -f ${.CURDIR}/etc/mtree/BSD.debug.dist \ -p ${DESTDIR}/${DISTDIR}/${dist}/usr/lib >/dev/null .endif .if defined(LIBCOMPAT) mtree -deU -f ${.CURDIR}/etc/mtree/BSD.lib${libcompat}.dist \ -p ${DESTDIR}/${DISTDIR}/${dist}/usr >/dev/null .if ${MK_DEBUG_FILES} != "no" mtree -deU -f ${.CURDIR}/etc/mtree/BSD.lib${libcompat}.dist \ -p ${DESTDIR}/${DISTDIR}/${dist}/usr/lib/debug/usr >/dev/null .endif .endif .if ${MK_TESTS} != "no" && ${dist} == "tests" -mkdir -p ${DESTDIR}/${DISTDIR}/${dist}${TESTSBASE} mtree -deU -f ${.CURDIR}/etc/mtree/BSD.tests.dist \ -p ${DESTDIR}/${DISTDIR}/${dist}${TESTSBASE} >/dev/null .if ${MK_DEBUG_FILES} != "no" mtree -deU -f ${.CURDIR}/etc/mtree/BSD.tests.dist \ -p ${DESTDIR}/${DISTDIR}/${dist}/usr/lib/debug/${TESTSBASE} >/dev/null .endif .endif .if defined(NO_ROOT) ${IMAKEENV} mtree -C -f ${.CURDIR}/etc/mtree/BSD.root.dist | \ sed -e 's#^\./#./${dist}/#' >> ${METALOG} ${IMAKEENV} mtree -C -f ${.CURDIR}/etc/mtree/BSD.usr.dist | \ sed -e 's#^\./#./${dist}/usr/#' >> ${METALOG} ${IMAKEENV} mtree -C -f ${.CURDIR}/etc/mtree/BSD.include.dist | \ sed -e 's#^\./#./${dist}/usr/include/#' >> ${METALOG} .if defined(LIBCOMPAT) ${IMAKEENV} mtree -C -f ${.CURDIR}/etc/mtree/BSD.lib${libcompat}.dist | \ sed -e 's#^\./#./${dist}/usr/#' >> ${METALOG} .endif .endif .endfor -mkdir ${DESTDIR}/${DISTDIR}/base ${_+_}cd ${.CURDIR}/etc; ${CROSSENV} PATH=${TMPPATH} ${MAKE} \ METALOG=${METALOG} ${IMAKE_INSTALL} ${IMAKE_MTREE} \ DISTBASE=/base DESTDIR=${DESTDIR}/${DISTDIR}/base \ LOCAL_MTREE=${LOCAL_MTREE:Q} distrib-dirs .endif ${_+_}cd ${.CURDIR}; ${IMAKE} re${.TARGET:S/world$//}; \ ${IMAKEENV} rm -rf ${INSTALLTMP} .if make(distributeworld) .for dist in ${EXTRA_DISTRIBUTIONS} find ${DESTDIR}/${DISTDIR}/${dist} -mindepth 1 -empty -delete .endfor .if defined(NO_ROOT) .for dist in base ${EXTRA_DISTRIBUTIONS} @# For each file that exists in this dist, print the corresponding @# line from the METALOG. This relies on the fact that @# a line containing only the filename will sort immediatly before @# the relevant mtree line. cd ${DESTDIR}/${DISTDIR}; \ find ./${dist} | sort -u ${METALOG} - | \ awk 'BEGIN { print "#${MTREE_MAGIC}" } !/ type=/ { file = $$1 } / type=/ { if ($$1 == file) { sub(/^\.\/${dist}\//, "./"); print } }' > \ ${DESTDIR}/${DISTDIR}/${dist}.meta .endfor .for dist in ${DEBUG_DISTRIBUTIONS} @# For each file that exists in this dist, print the corresponding @# line from the METALOG. This relies on the fact that @# a line containing only the filename will sort immediatly before @# the relevant mtree line. cd ${DESTDIR}/${DISTDIR}; \ find ./${dist}/usr/lib/debug | sort -u ${METALOG} - | \ awk 'BEGIN { print "#${MTREE_MAGIC}" } !/ type=/ { file = $$1 } / type=/ { if ($$1 == file) { sub(/^\.\/${dist}\//, "./"); print } }' > \ ${DESTDIR}/${DISTDIR}/${dist}.debug.meta .endfor .endif .endif packageworld: .for dist in base ${EXTRA_DISTRIBUTIONS} .if defined(NO_ROOT) ${_+_}cd ${DESTDIR}/${DISTDIR}/${dist}; \ tar cvf - --exclude usr/lib/debug \ @${DESTDIR}/${DISTDIR}/${dist}.meta | \ ${XZ_CMD} > ${PACKAGEDIR}/${dist}.txz .else ${_+_}cd ${DESTDIR}/${DISTDIR}/${dist}; \ tar cvf - --exclude usr/lib/debug . | \ ${XZ_CMD} > ${PACKAGEDIR}/${dist}.txz .endif .endfor .for dist in ${DEBUG_DISTRIBUTIONS} . if defined(NO_ROOT) ${_+_}cd ${DESTDIR}/${DISTDIR}/${dist}; \ tar cvf - @${DESTDIR}/${DISTDIR}/${dist}.debug.meta | \ ${XZ_CMD} > ${PACKAGEDIR}/${dist}-dbg.txz . else ${_+_}cd ${DESTDIR}/${DISTDIR}/${dist}; \ tar cvLf - usr/lib/debug | \ ${XZ_CMD} > ${PACKAGEDIR}/${dist}-dbg.txz . endif .endfor # # reinstall # # If you have a build server, you can NFS mount the source and obj directories # and do a 'make reinstall' on the *client* to install new binaries from the # most recent server build. # reinstall: .MAKE .PHONY @echo "--------------------------------------------------------------" @echo ">>> Making hierarchy" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; ${MAKE} -f Makefile.inc1 \ LOCAL_MTREE=${LOCAL_MTREE:Q} hierarchy @echo @echo "--------------------------------------------------------------" @echo ">>> Installing everything" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; ${MAKE} -f Makefile.inc1 install .if defined(LIBCOMPAT) ${_+_}cd ${.CURDIR}; ${MAKE} -f Makefile.inc1 install${libcompat} .endif restage: .MAKE .PHONY @echo "--------------------------------------------------------------" @echo ">>> Making hierarchy" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; ${MAKE} -f Makefile.inc1 \ LOCAL_MTREE=${LOCAL_MTREE:Q} hierarchy distribution @echo @echo "--------------------------------------------------------------" @echo ">>> Installing everything" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; ${MAKE} -f Makefile.inc1 install .if defined(LIB32TMP) && ${MK_LIB32} != "no" ${_+_}cd ${.CURDIR}; ${MAKE} -f Makefile.inc1 install32 .endif redistribute: .MAKE .PHONY @echo "--------------------------------------------------------------" @echo ">>> Distributing everything" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; ${MAKE} -f Makefile.inc1 distribute .if defined(LIBCOMPAT) ${_+_}cd ${.CURDIR}; ${MAKE} -f Makefile.inc1 distribute${libcompat} \ DISTRIBUTION=lib${libcompat} .endif distrib-dirs distribution: .MAKE .PHONY ${_+_}cd ${.CURDIR}/etc; ${CROSSENV} PATH=${TMPPATH} ${MAKE} \ ${IMAKE_INSTALL} ${IMAKE_MTREE} METALOG=${METALOG} ${.TARGET} .if make(distribution) ${_+_}cd ${.CURDIR}; ${CROSSENV} PATH=${TMPPATH} \ ${MAKE} -f Makefile.inc1 ${IMAKE_INSTALL} \ METALOG=${METALOG} MK_TESTS=no installconfig .endif # # buildkernel and installkernel # # Which kernels to build and/or install is specified by setting # KERNCONF. If not defined a GENERIC kernel is built/installed. # Only the existing (depending TARGET) config files are used # for building kernels and only the first of these is designated # as the one being installed. # # Note that we have to use TARGET instead of TARGET_ARCH when # we're in kernel-land. Since only TARGET_ARCH is (expected) to # be set to cross-build, we have to make sure TARGET is set # properly. .if defined(KERNFAST) NO_KERNELCLEAN= t NO_KERNELCONFIG= t NO_KERNELOBJ= t # Shortcut for KERNCONF=Blah -DKERNFAST is now KERNFAST=Blah .if !defined(KERNCONF) && ${KERNFAST} != "1" KERNCONF=${KERNFAST} .endif .endif .if ${TARGET_ARCH} == "powerpc64" KERNCONF?= GENERIC64 .else KERNCONF?= GENERIC .endif INSTKERNNAME?= kernel KERNSRCDIR?= ${.CURDIR}/sys KRNLCONFDIR= ${KERNSRCDIR}/${TARGET}/conf KRNLOBJDIR= ${OBJTREE}${KERNSRCDIR} KERNCONFDIR?= ${KRNLCONFDIR} BUILDKERNELS= INSTALLKERNEL= .if defined(NO_INSTALLKERNEL) # All of the BUILDKERNELS loops start at index 1. BUILDKERNELS+= dummy .endif .for _kernel in ${KERNCONF} .if exists(${KERNCONFDIR}/${_kernel}) BUILDKERNELS+= ${_kernel} .if empty(INSTALLKERNEL) && !defined(NO_INSTALLKERNEL) INSTALLKERNEL= ${_kernel} .endif .endif .endfor ${WMAKE_TGTS:N_worldtmp:Nbuild${libcompat}} ${.ALLTARGETS:M_*:N_worldtmp}: .MAKE .PHONY # # buildkernel # # Builds all kernels defined by BUILDKERNELS. # buildkernel: .MAKE .PHONY .if empty(BUILDKERNELS:Ndummy) @echo "ERROR: Missing kernel configuration file(s) (${KERNCONF})."; \ false .endif @echo .for _kernel in ${BUILDKERNELS:Ndummy} @echo "--------------------------------------------------------------" @echo ">>> Kernel build for ${_kernel} started on `LC_ALL=C date`" @echo "--------------------------------------------------------------" @echo "===> ${_kernel}" mkdir -p ${KRNLOBJDIR} .if !defined(NO_KERNELCONFIG) @echo @echo "--------------------------------------------------------------" @echo ">>> stage 1: configuring the kernel" @echo "--------------------------------------------------------------" cd ${KRNLCONFDIR}; \ PATH=${TMPPATH} \ config ${CONFIGARGS} -d ${KRNLOBJDIR}/${_kernel} \ -I '${KERNCONFDIR}' '${KERNCONFDIR}/${_kernel}' .endif .if !defined(NO_CLEAN) && !defined(NO_KERNELCLEAN) @echo @echo "--------------------------------------------------------------" @echo ">>> stage 2.1: cleaning up the object tree" @echo "--------------------------------------------------------------" ${_+_}cd ${KRNLOBJDIR}/${_kernel}; ${KMAKE} ${CLEANDIR} .endif .if !defined(NO_KERNELOBJ) @echo @echo "--------------------------------------------------------------" @echo ">>> stage 2.2: rebuilding the object tree" @echo "--------------------------------------------------------------" ${_+_}cd ${KRNLOBJDIR}/${_kernel}; ${KMAKE} obj .endif @echo @echo "--------------------------------------------------------------" @echo ">>> stage 2.3: build tools" @echo "--------------------------------------------------------------" ${_+_}cd ${.CURDIR}; ${KTMAKE} kernel-tools @echo @echo "--------------------------------------------------------------" @echo ">>> stage 3.1: building everything" @echo "--------------------------------------------------------------" ${_+_}cd ${KRNLOBJDIR}/${_kernel}; ${KMAKE} all -DNO_MODULES_OBJ @echo "--------------------------------------------------------------" @echo ">>> Kernel build for ${_kernel} completed on `LC_ALL=C date`" @echo "--------------------------------------------------------------" .endfor # # installkernel, etc. # # Install the kernel defined by INSTALLKERNEL # installkernel installkernel.debug \ reinstallkernel reinstallkernel.debug: _installcheck_kernel .if !defined(NO_INSTALLKERNEL) .if empty(INSTALLKERNEL) @echo "ERROR: No kernel \"${KERNCONF}\" to install."; \ false .endif @echo "--------------------------------------------------------------" @echo ">>> Installing kernel ${INSTALLKERNEL}" @echo "--------------------------------------------------------------" cd ${KRNLOBJDIR}/${INSTALLKERNEL}; \ ${CROSSENV} PATH=${TMPPATH} \ ${MAKE} ${IMAKE_INSTALL} KERNEL=${INSTKERNNAME} ${.TARGET:S/kernel//} .endif .if ${BUILDKERNELS:[#]} > 1 && !defined(NO_INSTALLEXTRAKERNELS) .for _kernel in ${BUILDKERNELS:[2..-1]} @echo "--------------------------------------------------------------" @echo ">>> Installing kernel ${_kernel}" @echo "--------------------------------------------------------------" cd ${KRNLOBJDIR}/${_kernel}; \ ${CROSSENV} PATH=${TMPPATH} \ ${MAKE} ${IMAKE_INSTALL} KERNEL=${INSTKERNNAME}.${_kernel} ${.TARGET:S/kernel//} .endfor .endif distributekernel distributekernel.debug: .if !defined(NO_INSTALLKERNEL) .if empty(INSTALLKERNEL) @echo "ERROR: No kernel \"${KERNCONF}\" to install."; \ false .endif mkdir -p ${DESTDIR}/${DISTDIR} .if defined(NO_ROOT) @echo "#${MTREE_MAGIC}" > ${DESTDIR}/${DISTDIR}/kernel.premeta .endif cd ${KRNLOBJDIR}/${INSTALLKERNEL}; \ ${IMAKEENV} ${IMAKE_INSTALL:S/METALOG/kernel.premeta/} \ ${IMAKE_MTREE} PATH=${TMPPATH} ${MAKE} KERNEL=${INSTKERNNAME} \ DESTDIR=${INSTALL_DDIR}/kernel \ ${.TARGET:S/distributekernel/install/} .if defined(NO_ROOT) @sed -e 's|^./kernel|.|' ${DESTDIR}/${DISTDIR}/kernel.premeta > \ ${DESTDIR}/${DISTDIR}/kernel.meta .endif .endif .if ${BUILDKERNELS:[#]} > 1 && !defined(NO_INSTALLEXTRAKERNELS) .for _kernel in ${BUILDKERNELS:[2..-1]} .if defined(NO_ROOT) @echo "#${MTREE_MAGIC}" > ${DESTDIR}/${DISTDIR}/kernel.${_kernel}.premeta .endif cd ${KRNLOBJDIR}/${_kernel}; \ ${IMAKEENV} ${IMAKE_INSTALL:S/METALOG/kernel.${_kernel}.premeta/} \ ${IMAKE_MTREE} PATH=${TMPPATH} ${MAKE} \ KERNEL=${INSTKERNNAME}.${_kernel} \ DESTDIR=${INSTALL_DDIR}/kernel.${_kernel} \ ${.TARGET:S/distributekernel/install/} .if defined(NO_ROOT) @sed -e "s|^./kernel.${_kernel}|.|" \ ${DESTDIR}/${DISTDIR}/kernel.${_kernel}.premeta > \ ${DESTDIR}/${DISTDIR}/kernel.${_kernel}.meta .endif .endfor .endif packagekernel: .if defined(NO_ROOT) .if !defined(NO_INSTALLKERNEL) cd ${DESTDIR}/${DISTDIR}/kernel; \ tar cvf - --exclude '*.debug' \ @${DESTDIR}/${DISTDIR}/kernel.meta | \ ${XZ_CMD} > ${PACKAGEDIR}/kernel.txz .endif cd ${DESTDIR}/${DISTDIR}/kernel; \ tar cvf - --include '*/*/*.debug' \ @${DESTDIR}/${DISTDIR}/kernel.meta | \ ${XZ_CMD} > ${DESTDIR}/${DISTDIR}/kernel-dbg.txz .if ${BUILDKERNELS:[#]} > 1 && !defined(NO_INSTALLEXTRAKERNELS) .for _kernel in ${BUILDKERNELS:[2..-1]} cd ${DESTDIR}/${DISTDIR}/kernel.${_kernel}; \ tar cvf - --exclude '*.debug' \ @${DESTDIR}/${DISTDIR}/kernel.${_kernel}.meta | \ ${XZ_CMD} > ${PACKAGEDIR}/kernel.${_kernel}.txz cd ${DESTDIR}/${DISTDIR}/kernel.${_kernel}; \ tar cvf - --include '*/*/*.debug' \ @${DESTDIR}/${DISTDIR}/kernel.${_kernel}.meta | \ ${XZ_CMD} > ${DESTDIR}/${DISTDIR}/kernel.${_kernel}-dbg.txz .endfor .endif .else .if !defined(NO_INSTALLKERNEL) cd ${DESTDIR}/${DISTDIR}/kernel; \ tar cvf - --exclude '*.debug' . | \ ${XZ_CMD} > ${PACKAGEDIR}/kernel.txz .endif cd ${DESTDIR}/${DISTDIR}/kernel; \ tar cvf - --include '*/*/*.debug' $$(eval find .) | \ ${XZ_CMD} > ${DESTDIR}/${DISTDIR}/kernel-dbg.txz .if ${BUILDKERNELS:[#]} > 1 && !defined(NO_INSTALLEXTRAKERNELS) .for _kernel in ${BUILDKERNELS:[2..-1]} cd ${DESTDIR}/${DISTDIR}/kernel.${_kernel}; \ tar cvf - --exclude '*.debug' . | \ ${XZ_CMD} > ${PACKAGEDIR}/kernel.${_kernel}.txz cd ${DESTDIR}/${DISTDIR}/kernel.${_kernel}; \ tar cvf - --include '*/*/*.debug' $$(eval find .) | \ ${XZ_CMD} > ${DESTDIR}/${DISTDIR}/kernel.${_kernel}-dbg.txz .endfor .endif .endif stagekernel: ${MAKE} -C ${.CURDIR} ${.MAKEFLAGS} distributekernel PORTSDIR?= /usr/ports WSTAGEDIR?= ${MAKEOBJDIRPREFIX}${.CURDIR}/${TARGET}.${TARGET_ARCH}/worldstage KSTAGEDIR?= ${MAKEOBJDIRPREFIX}${.CURDIR}/${TARGET}.${TARGET_ARCH}/kernelstage REPODIR?= ${MAKEOBJDIRPREFIX}${.CURDIR}/repo PKGSIGNKEY?= # empty _pkgbootstrap: .if !exists(${LOCALBASE}/sbin/pkg) @env ASSUME_ALWAYS_YES=YES pkg bootstrap .endif packages: ${MAKE} -C ${.CURDIR} PKG_VERSION=${PKG_VERSION} real-packages package-pkg: rm -rf /tmp/ports.${TARGET} || : env ${WMAKEENV:Q} SRCDIR=${.CURDIR} PORTSDIR=${PORTSDIR} REVISION=${REVISION} \ PKG_VERSION=${PKG_VERSION} REPODIR=${REPODIR} WSTAGEDIR=${WSTAGEDIR} \ sh ${.CURDIR}/release/scripts/make-pkg-package.sh real-packages: stage-packages create-packages sign-packages stage-packages: @mkdir -p ${WSTAGEDIR} ${KSTAGEDIR} ${_+_}@cd ${.CURDIR}; \ ${MAKE} DESTDIR=${WSTAGEDIR} -DNO_ROOT -B stageworld ; \ ${MAKE} DESTDIR=${KSTAGEDIR} -DNO_ROOT -B stagekernel create-packages: _pkgbootstrap @mkdir -p ${REPODIR} ${_+_}@cd ${.CURDIR}; \ ${MAKE} DESTDIR=${WSTAGEDIR} \ PKG_VERSION=${PKG_VERSION} create-world-packages ; \ ${MAKE} DESTDIR=${KSTAGEDIR} \ PKG_VERSION=${PKG_VERSION} DISTDIR=kernel \ create-kernel-packages create-world-packages: _pkgbootstrap @rm -f ${DESTDIR}/*.plist 2>/dev/null || : @cd ${DESTDIR} ; \ awk -f ${SRCDIR}/release/scripts/mtree-to-plist.awk \ ${DESTDIR}/METALOG @for plist in ${DESTDIR}/*.plist; do \ plist=$${plist##*/} ; \ pkgname=$${plist%.plist} ; \ sh ${SRCDIR}/release/packages/generate-ucl.sh -o $${pkgname} \ -s ${SRCDIR} -u ${DESTDIR}/$${pkgname}.ucl ; \ done @for plist in ${DESTDIR}/*.plist; do \ plist=$${plist##*/} ; \ pkgname=$${plist%.plist} ; \ awk -F\" ' \ /^name/ { printf("===> Creating %s-", $$2); next } \ /^version/ { print $$2; next } \ ' ${DESTDIR}/$${pkgname}.ucl ; \ pkg -o ABI_FILE=${DESTDIR}/bin/sh -o ALLOW_BASE_SHLIBS=yes \ create -M ${DESTDIR}/$${pkgname}.ucl \ -p ${DESTDIR}/$${pkgname}.plist \ -r ${DESTDIR} \ -o ${REPODIR}/$$(pkg -o ABI_FILE=${DESTDIR}/bin/sh config ABI)/${PKG_VERSION} ; \ done create-kernel-packages: _pkgbootstrap .if exists(${DESTDIR}/kernel.meta) .for flavor in "" -debug @cd ${DESTDIR}/${DISTDIR} ; \ awk -f ${SRCDIR}/release/scripts/mtree-to-plist.awk \ -v kernel=yes -v _kernconf=${INSTALLKERNEL} \ ${DESTDIR}/kernel.meta ; \ cap_arg=`cd ${SRCDIR}/etc ; ${MAKE} -VCAP_MKDB_ENDIAN` ; \ pwd_arg=`cd ${SRCDIR}/etc ; ${MAKE} -VPWD_MKDB_ENDIAN` ; \ sed -e "s/%VERSION%/${PKG_VERSION}/" \ -e "s/%PKGNAME%/kernel-${INSTALLKERNEL:tl}${flavor}/" \ -e "s/%COMMENT%/FreeBSD ${INSTALLKERNEL} kernel ${flavor}/" \ -e "s/%DESC%/FreeBSD ${INSTALLKERNEL} kernel ${flavor}/" \ -e "s/%CAP_MKDB_ENDIAN%/$${cap_arg}/g" \ -e "s/%PWD_MKDB_ENDIAN%/$${pwd_arg}/g" \ ${SRCDIR}/release/packages/kernel.ucl \ > ${DESTDIR}/${DISTDIR}/kernel.${INSTALLKERNEL}${flavor}.ucl ; \ awk -F\" ' \ /name/ { printf("===> Creating %s-", $$2); next } \ /version/ {print $$2; next } ' \ ${DESTDIR}/${DISTDIR}/kernel.${INSTALLKERNEL}${flavor}.ucl ; \ pkg -o ABI_FILE=${WSTAGEDIR}/bin/sh -o ALLOW_BASE_SHLIBS=yes \ create -M ${DESTDIR}/${DISTDIR}/kernel.${INSTALLKERNEL}${flavor}.ucl \ -p ${DESTDIR}/${DISTDIR}/kernel.${INSTALLKERNEL}${flavor}.plist \ -r ${DESTDIR}/${DISTDIR} \ -o ${REPODIR}/$$(pkg -o ABI_FILE=${WSTAGEDIR}/bin/sh config ABI)/${PKG_VERSION} .endfor .endif .if ${BUILDKERNELS:[#]} > 1 && !defined(NO_INSTALLEXTRAKERNELS) .for _kernel in ${BUILDKERNELS:[2..-1]} .if exists(${DESTDIR}/kernel.${_kernel}.meta) .for flavor in "" -debug @cd ${DESTDIR}/kernel.${_kernel} ; \ awk -f ${SRCDIR}/release/scripts/mtree-to-plist.awk \ -v kernel=yes -v _kernconf=${_kernel} \ ${DESTDIR}/kernel.${_kernel}.meta ; \ cap_arg=`cd ${SRCDIR}/etc ; ${MAKE} -VCAP_MKDB_ENDIAN` ; \ pwd_arg=`cd ${SRCDIR}/etc ; ${MAKE} -VPWD_MKDB_ENDIAN` ; \ sed -e "s/%VERSION%/${PKG_VERSION}/" \ -e "s/%PKGNAME%/kernel-${_kernel:tl}${flavor}/" \ -e "s/%COMMENT%/FreeBSD ${_kernel} kernel ${flavor}/" \ -e "s/%DESC%/FreeBSD ${_kernel} kernel ${flavor}/" \ -e "s/%CAP_MKDB_ENDIAN%/$${cap_arg}/g" \ -e "s/%PWD_MKDB_ENDIAN%/$${pwd_arg}/g" \ ${SRCDIR}/release/packages/kernel.ucl \ > ${DESTDIR}/kernel.${_kernel}/kernel.${_kernel}${flavor}.ucl ; \ awk -F\" ' \ /name/ { printf("===> Creating %s-", $$2); next } \ /version/ {print $$2; next } ' \ ${DESTDIR}/kernel.${_kernel}/kernel.${_kernel}${flavor}.ucl ; \ pkg -o ABI_FILE=${WSTAGEDIR}/bin/sh -o ALLOW_BASE_SHLIBS=yes \ create -M ${DESTDIR}/kernel.${_kernel}/kernel.${_kernel}${flavor}.ucl \ -p ${DESTDIR}/kernel.${_kernel}/kernel.${_kernel}${flavor}.plist \ -r ${DESTDIR}/kernel.${_kernel} \ -o ${REPODIR}/$$(pkg -o ABI_FILE=${WSTAGEDIR}/bin/sh config ABI)/${PKG_VERSION} .endfor .endif .endfor .endif sign-packages: _pkgbootstrap @[ -L "${REPODIR}/$$(pkg -o ABI_FILE=${WSTAGEDIR}/bin/sh config ABI)/latest" ] && \ unlink ${REPODIR}/$$(pkg -o ABI_FILE=${WSTAGEDIR}/bin/sh config ABI)/latest ; \ pkg -o ABI_FILE=${WSTAGEDIR}/bin/sh repo \ -o ${REPODIR}/$$(pkg -o ABI_FILE=${WSTAGEDIR}/bin/sh config ABI)/${PKG_VERSION} \ ${REPODIR}/$$(pkg -o ABI_FILE=${WSTAGEDIR}/bin/sh config ABI)/${PKG_VERSION} \ ${PKGSIGNKEY} ; \ ln -s ${REPODIR}/$$(pkg -o ABI_FILE=${WSTAGEDIR}/bin/sh config ABI)/${PKG_VERSION} \ ${REPODIR}/$$(pkg -o ABI_FILE=${WSTAGEDIR}/bin/sh config ABI)/latest # # # checkworld # # Run test suite on installed world. # checkworld: .PHONY @if [ ! -x ${LOCALBASE}/bin/kyua ]; then \ echo "You need kyua (devel/kyua) to run the test suite." | /usr/bin/fmt; \ exit 1; \ fi ${_+_}${LOCALBASE}/bin/kyua test -k ${TESTSBASE}/Kyuafile # # # doxygen # # Build the API documentation with doxygen # doxygen: .PHONY @if [ ! -x ${LOCALBASE}/bin/doxygen ]; then \ echo "You need doxygen (devel/doxygen) to generate the API documentation of the kernel." | /usr/bin/fmt; \ exit 1; \ fi ${_+_}cd ${.CURDIR}/tools/kerneldoc/subsys; ${MAKE} obj all # # update # # Update the source tree(s), by running svn/svnup to update to the # latest copy. # update: .if (defined(CVS_UPDATE) || defined(SUP_UPDATE)) && !defined(SVN_UPDATE) @echo "--------------------------------------------------------------" @echo "CVS_UPDATE and SUP_UPDATE are no longer supported." @echo "Please see: https://wiki.freebsd.org/CvsIsDeprecated" @echo "--------------------------------------------------------------" @exit 1 .endif .if defined(SVN_UPDATE) @echo "--------------------------------------------------------------" @echo ">>> Updating ${.CURDIR} using Subversion" @echo "--------------------------------------------------------------" @(cd ${.CURDIR}; ${SVN} update ${SVNFLAGS}) .endif # # ------------------------------------------------------------------------ # # From here onwards are utility targets used by the 'make world' and # related targets. If your 'world' breaks, you may like to try to fix # the problem and manually run the following targets to attempt to # complete the build. Beware, this is *not* guaranteed to work, you # need to have a pretty good grip on the current state of the system # to attempt to manually finish it. If in doubt, 'make world' again. # # # legacy: Build compatibility shims for the next three targets. This is a # minimal set of tools and shims necessary to compensate for older systems # which don't have the APIs required by the targets built in bootstrap-tools, # build-tools or cross-tools. # # ELF Tool Chain libraries are needed for ELF tools and dtrace tools. # r296685 fix cross-endian objcopy .if ${BOOTSTRAPPING} < 1100102 _elftoolchain_libs= lib/libelf lib/libdwarf .endif legacy: .if ${BOOTSTRAPPING} < 800107 && ${BOOTSTRAPPING} != 0 @echo "ERROR: Source upgrades from versions prior to 8.0 are not supported."; \ false .endif .for _tool in tools/build ${_elftoolchain_libs} ${_+_}@${ECHODIR} "===> ${_tool} (obj,includes,all,install)"; \ cd ${.CURDIR}/${_tool}; \ ${MAKE} DIRPRFX=${_tool}/ obj; \ ${MAKE} DIRPRFX=${_tool}/ DESTDIR=${MAKEOBJDIRPREFIX}/legacy includes; \ ${MAKE} DIRPRFX=${_tool}/ all; \ ${MAKE} DIRPRFX=${_tool}/ DESTDIR=${MAKEOBJDIRPREFIX}/legacy install .endfor # # bootstrap-tools: Build tools needed for compatibility. These are binaries that # are built to build other binaries in the system. However, the focus of these # binaries is usually quite narrow. Bootstrap tools use the host's compiler and # libraries, augmented by -legacy. # _bt= _bootstrap-tools .if ${MK_GAMES} != "no" _strfile= usr.bin/fortune/strfile .endif .if ${MK_GCC} != "no" && ${MK_CXX} != "no" _gperf= gnu/usr.bin/gperf .endif .if ${MK_GROFF} != "no" _groff= gnu/usr.bin/groff \ usr.bin/soelim .endif .if ${MK_VT} != "no" _vtfontcvt= usr.bin/vtfontcvt .endif .if ${BOOTSTRAPPING} < 900002 _sed= usr.bin/sed .endif .if ${BOOTSTRAPPING} < 1000033 _libopenbsd= lib/libopenbsd _m4= usr.bin/m4 _lex= usr.bin/lex ${_bt}-usr.bin/m4: ${_bt}-lib/libopenbsd ${_bt}-usr.bin/lex: ${_bt}-usr.bin/m4 .endif .if ${BOOTSTRAPPING} < 1000026 _nmtree= lib/libnetbsd \ usr.sbin/nmtree ${_bt}-usr.sbin/nmtree: ${_bt}-lib/libnetbsd .endif .if ${BOOTSTRAPPING} < 1000027 _cat= bin/cat .endif # r264059 support for status= .if ${BOOTSTRAPPING} < 1100017 _dd= bin/dd .endif # r277259 crunchide: Correct 64-bit section header offset # r281674 crunchide: always include both 32- and 64-bit ELF support # r285986 crunchen: use STRIPBIN rather than STRIP .if ${BOOTSTRAPPING} < 1100078 _crunch= usr.sbin/crunch .endif .if ${BOOTSTRAPPING} >= 900040 && ${BOOTSTRAPPING} < 900041 _awk= usr.bin/awk .endif # r296926 -P keymap search path .if ${BOOTSTRAPPING} < 1100103 _kbdcontrol= usr.sbin/kbdcontrol .endif _yacc= lib/liby \ usr.bin/yacc ${_bt}-usr.bin/yacc: ${_bt}-lib/liby .if ${MK_BSNMP} != "no" _gensnmptree= usr.sbin/bsnmpd/gensnmptree .endif # We need to build tblgen when we're building clang either as # the bootstrap compiler, or as the part of the normal build. .if ${MK_CLANG_BOOTSTRAP} != "no" || ${MK_CLANG} != "no" _clang_tblgen= \ lib/clang/libllvmsupport \ lib/clang/libllvmtablegen \ usr.bin/clang/llvm-tblgen \ usr.bin/clang/clang-tblgen ${_bt}-usr.bin/clang/clang-tblgen: ${_bt}-lib/clang/libllvmtablegen ${_bt}-lib/clang/libllvmsupport ${_bt}-usr.bin/clang/llvm-tblgen: ${_bt}-lib/clang/libllvmtablegen ${_bt}-lib/clang/libllvmsupport .endif # Default to building the GPL DTC, but build the BSDL one if users explicitly # request it. _dtc= usr.bin/dtc .if ${MK_GPL_DTC} != "no" _dtc= gnu/usr.bin/dtc .endif .if ${MK_KERBEROS} != "no" _kerberos5_bootstrap_tools= \ kerberos5/tools/make-roken \ kerberos5/lib/libroken \ kerberos5/lib/libvers \ kerberos5/tools/asn1_compile \ kerberos5/tools/slc \ usr.bin/compile_et .ORDER: ${_kerberos5_bootstrap_tools:C/^/${_bt}-/g} .endif # r283777 makewhatis(1) replaced with mandoc version which builds a database. .if ${MK_MANDOCDB} != "no" && ${BOOTSTRAPPING} < 1100075 _libopenbsd?= lib/libopenbsd _makewhatis= lib/libsqlite3 \ usr.bin/mandoc ${_bt}-usr.bin/mandoc: ${_bt}-lib/libopenbsd ${_bt}-lib/libsqlite3 .endif bootstrap-tools: .PHONY # Please document (add comment) why something is in 'bootstrap-tools'. # Try to bound the building of the bootstrap-tool to just the # FreeBSD versions that need the tool built at this stage of the build. .for _tool in \ ${_clang_tblgen} \ ${_kerberos5_bootstrap_tools} \ ${_strfile} \ ${_gperf} \ ${_groff} \ ${_dtc} \ ${_awk} \ ${_cat} \ ${_dd} \ ${_kbdcontrol} \ usr.bin/lorder \ ${_libopenbsd} \ ${_makewhatis} \ usr.bin/rpcgen \ ${_sed} \ ${_yacc} \ ${_m4} \ ${_lex} \ usr.bin/xinstall \ ${_gensnmptree} \ usr.sbin/config \ ${_crunch} \ ${_nmtree} \ ${_vtfontcvt} \ usr.bin/localedef ${_bt}-${_tool}: .PHONY .MAKE ${_+_}@${ECHODIR} "===> ${_tool} (obj,all,install)"; \ cd ${.CURDIR}/${_tool}; \ ${MAKE} DIRPRFX=${_tool}/ obj; \ ${MAKE} DIRPRFX=${_tool}/ all; \ ${MAKE} DIRPRFX=${_tool}/ DESTDIR=${MAKEOBJDIRPREFIX}/legacy install bootstrap-tools: ${_bt}-${_tool} .endfor # # build-tools: Build special purpose build tools # .if !defined(NO_SHARE) _share= share/syscons/scrnmaps .endif .if ${MK_GCC} != "no" _gcc_tools= gnu/usr.bin/cc/cc_tools .endif .if ${MK_RESCUE} != "no" # rescue includes programs that have build-tools targets _rescue=rescue/rescue .endif .for _tool in \ bin/csh \ bin/sh \ ${LOCAL_TOOL_DIRS} \ lib/ncurses/ncurses \ lib/ncurses/ncursesw \ ${_rescue} \ ${_share} \ usr.bin/awk \ lib/libmagic \ usr.bin/mkesdb_static \ usr.bin/mkcsmapper_static \ usr.bin/vi/catalog build-tools_${_tool}: .PHONY ${_+_}@${ECHODIR} "===> ${_tool} (obj,build-tools)"; \ cd ${.CURDIR}/${_tool}; \ ${MAKE} DIRPRFX=${_tool}/ obj; \ ${MAKE} DIRPRFX=${_tool}/ build-tools build-tools: build-tools_${_tool} .endfor .for _tool in \ ${_gcc_tools} build-tools_${_tool}: .PHONY ${_+_}@${ECHODIR} "===> ${_tool} (obj,all)"; \ cd ${.CURDIR}/${_tool}; \ ${MAKE} DIRPRFX=${_tool}/ obj; \ ${MAKE} DIRPRFX=${_tool}/ all build-tools: build-tools_${_tool} .endfor # # kernel-tools: Build kernel-building tools # kernel-tools: mkdir -p ${MAKEOBJDIRPREFIX}/usr mtree -deU -f ${.CURDIR}/etc/mtree/BSD.usr.dist \ -p ${MAKEOBJDIRPREFIX}/usr >/dev/null # # cross-tools: All the tools needed to build the rest of the system after # we get done with the earlier stages. It is the last set of tools needed # to begin building the target binaries. # .if ${TARGET_ARCH} != ${MACHINE_ARCH} .if ${TARGET_ARCH} == "amd64" || ${TARGET_ARCH} == "i386" _btxld= usr.sbin/btxld .endif .endif # Rebuild ctfconvert and ctfmerge to avoid difficult-to-diagnose failures # resulting from missing bug fixes or ELF Toolchain updates. .if ${MK_CDDL} != "no" _dtrace_tools= cddl/lib/libctf cddl/usr.bin/ctfconvert \ cddl/usr.bin/ctfmerge .endif # If we're given an XAS, don't build binutils. .if ${XAS:M/*} == "" .if ${MK_BINUTILS_BOOTSTRAP} != "no" _binutils= gnu/usr.bin/binutils .endif .if ${MK_ELFTOOLCHAIN_BOOTSTRAP} != "no" _elftctools= lib/libelftc \ lib/libpe \ usr.bin/elfcopy \ usr.bin/nm \ usr.bin/size \ usr.bin/strings # These are not required by the build, but can be useful for developers who # cross-build on a FreeBSD 10 host: _elftctools+= usr.bin/addr2line .endif .elif ${TARGET_ARCH} != ${MACHINE_ARCH} && ${MK_ELFTOOLCHAIN_BOOTSTRAP} != "no" # If cross-building with an external binutils we still need to build strip for # the target (for at least crunchide). _elftctools= lib/libelftc \ lib/libpe \ usr.bin/elfcopy .endif # If an full path to an external cross compiler is given, don't build # a cross compiler. .if ${XCC:N${CCACHE_BIN}:M/*} == "" && ${MK_CROSS_COMPILER} != "no" .if ${MK_CLANG_BOOTSTRAP} != "no" _clang= usr.bin/clang _clang_libs= lib/clang .endif .if ${MK_GCC_BOOTSTRAP} != "no" _cc= gnu/usr.bin/cc .endif .endif .if ${MK_USB} != "no" _usb_tools= sys/boot/usb/tools .endif cross-tools: .MAKE .PHONY .for _tool in \ ${_clang_libs} \ ${_clang} \ ${_binutils} \ ${_elftctools} \ ${_dtrace_tools} \ ${_cc} \ ${_btxld} \ ${_crunchide} \ ${_usb_tools} ${_+_}@${ECHODIR} "===> ${_tool} (obj,all,install)"; \ cd ${.CURDIR}/${_tool}; \ ${MAKE} DIRPRFX=${_tool}/ obj; \ ${MAKE} DIRPRFX=${_tool}/ all; \ ${MAKE} DIRPRFX=${_tool}/ DESTDIR=${MAKEOBJDIRPREFIX} install .endfor NXBDESTDIR= ${OBJTREE}/nxb-bin NXBENV= MAKEOBJDIRPREFIX=${OBJTREE}/nxb \ INSTALL="sh ${.CURDIR}/tools/install.sh" \ PATH=${PATH}:${OBJTREE}/gperf_for_gcc/usr/bin NXBMAKE= ${NXBENV} ${MAKE} \ LLVM_TBLGEN=${NXBDESTDIR}/usr/bin/llvm-tblgen \ CLANG_TBLGEN=${NXBDESTDIR}/usr/bin/clang-tblgen \ MACHINE=${TARGET} MACHINE_ARCH=${TARGET_ARCH} \ MK_GDB=no MK_TESTS=no \ SSP_CFLAGS= \ MK_HTML=no NO_LINT=yes MK_MAN=no \ -DNO_PIC MK_PROFILE=no -DNO_SHARED \ -DNO_CPU_CFLAGS MK_WARNS=no MK_CTF=no \ MK_CLANG_EXTRAS=no MK_CLANG_FULL=no \ MK_LLDB=no MK_DEBUG_FILES=no # native-xtools is the current target for qemu-user cross builds of ports # via poudriere and the imgact_binmisc kernel module. # For non-clang enabled targets that are still using the in tree gcc # we must build a gperf binary for one instance of its Makefiles. On # clang-enabled systems, the gperf binary is obsolete. native-xtools: .PHONY .if ${MK_GCC_BOOTSTRAP} != "no" mkdir -p ${OBJTREE}/gperf_for_gcc/usr/bin ${_+_}@${ECHODIR} "===> ${_gperf} (obj,all,install)"; \ cd ${.CURDIR}/${_gperf}; \ ${NXBMAKE} DIRPRFX=${_gperf}/ obj; \ ${NXBMAKE} DIRPRFX=${_gperf}/ all; \ ${NXBMAKE} DIRPRFX=${_gperf}/ DESTDIR=${OBJTREE}/gperf_for_gcc install .endif mkdir -p ${NXBDESTDIR}/bin ${NXBDESTDIR}/sbin ${NXBDESTDIR}/usr mtree -deU -f ${.CURDIR}/etc/mtree/BSD.usr.dist \ -p ${NXBDESTDIR}/usr >/dev/null mtree -deU -f ${.CURDIR}/etc/mtree/BSD.include.dist \ -p ${NXBDESTDIR}/usr/include >/dev/null .if ${MK_DEBUG_FILES} != "no" mtree -deU -f ${.CURDIR}/etc/mtree/BSD.debug.dist \ -p ${NXBDESTDIR}/usr/lib >/dev/null .endif .for _tool in \ bin/cat \ bin/chmod \ bin/cp \ bin/csh \ bin/echo \ bin/expr \ bin/hostname \ bin/ln \ bin/ls \ bin/mkdir \ bin/mv \ bin/ps \ bin/realpath \ bin/rm \ bin/rmdir \ bin/sh \ bin/sleep \ ${_clang_tblgen} \ usr.bin/ar \ ${_binutils} \ ${_elftctools} \ ${_cc} \ ${_gcc_tools} \ ${_clang_libs} \ ${_clang} \ sbin/md5 \ sbin/sysctl \ gnu/usr.bin/diff \ usr.bin/awk \ usr.bin/basename \ usr.bin/bmake \ usr.bin/bzip2 \ usr.bin/cmp \ usr.bin/dirname \ usr.bin/env \ usr.bin/fetch \ usr.bin/find \ usr.bin/grep \ usr.bin/gzip \ usr.bin/id \ usr.bin/lex \ usr.bin/lorder \ usr.bin/mktemp \ usr.bin/mt \ usr.bin/patch \ usr.bin/sed \ usr.bin/sort \ usr.bin/tar \ usr.bin/touch \ usr.bin/tr \ usr.bin/true \ usr.bin/uniq \ usr.bin/unzip \ usr.bin/xargs \ usr.bin/xinstall \ usr.bin/xz \ usr.bin/yacc \ usr.sbin/chown ${_+_}@${ECHODIR} "===> ${_tool} (obj,all,install)"; \ cd ${.CURDIR}/${_tool}; \ ${NXBMAKE} DIRPRFX=${_tool}/ obj; \ ${NXBMAKE} DIRPRFX=${_tool}/ all; \ ${NXBMAKE} DIRPRFX=${_tool}/ DESTDIR=${NXBDESTDIR} install .endfor # # hierarchy - ensure that all the needed directories are present # hierarchy hier: .MAKE .PHONY ${_+_}cd ${.CURDIR}/etc; ${HMAKE} distrib-dirs # # libraries - build all libraries, and install them under ${DESTDIR}. # # The list of libraries with dependents (${_prebuild_libs}) and their # interdependencies (__L) are built automatically by the # ${.CURDIR}/tools/make_libdeps.sh script. # libraries: .MAKE .PHONY ${_+_}cd ${.CURDIR}; \ ${MAKE} -f Makefile.inc1 _prereq_libs; \ ${MAKE} -f Makefile.inc1 _startup_libs; \ ${MAKE} -f Makefile.inc1 _prebuild_libs; \ ${MAKE} -f Makefile.inc1 _generic_libs # # static libgcc.a prerequisite for shared libc # _prereq_libs= gnu/lib/libssp/libssp_nonshared gnu/lib/libgcc lib/libcompiler_rt # These dependencies are not automatically generated: # # gnu/lib/csu, gnu/lib/libgcc, lib/csu and lib/libc must be built before # all shared libraries for ELF. # _startup_libs= gnu/lib/csu _startup_libs+= lib/csu _startup_libs+= gnu/lib/libgcc _startup_libs+= lib/libcompiler_rt _startup_libs+= lib/libc _startup_libs+= lib/libc_nonshared .if ${MK_LIBCPLUSPLUS} != "no" _startup_libs+= lib/libcxxrt .endif gnu/lib/libgcc__L: lib/libc__L gnu/lib/libgcc__L: lib/libc_nonshared__L .if ${MK_LIBCPLUSPLUS} != "no" lib/libcxxrt__L: gnu/lib/libgcc__L .endif _prebuild_libs= ${_kerberos5_lib_libasn1} \ ${_kerberos5_lib_libhdb} \ ${_kerberos5_lib_libheimbase} \ ${_kerberos5_lib_libheimntlm} \ ${_libsqlite3} \ ${_kerberos5_lib_libheimipcc} \ ${_kerberos5_lib_libhx509} ${_kerberos5_lib_libkrb5} \ ${_kerberos5_lib_libroken} \ ${_kerberos5_lib_libwind} \ lib/libbz2 ${_libcom_err} lib/libcrypt \ lib/libelf lib/libexpat \ lib/libfigpar \ ${_lib_libgssapi} \ lib/libkiconv lib/libkvm lib/liblzma lib/libmd lib/libnv \ ${_lib_casper} \ lib/ncurses/ncurses lib/ncurses/ncursesw \ lib/libopie lib/libpam ${_lib_libthr} \ ${_lib_libradius} lib/libsbuf lib/libtacplus \ lib/libgeom \ ${_cddl_lib_libumem} ${_cddl_lib_libnvpair} \ ${_cddl_lib_libuutil} \ ${_cddl_lib_libavl} \ ${_cddl_lib_libzfs_core} \ ${_cddl_lib_libctf} \ lib/libutil lib/libpjdlog ${_lib_libypclnt} lib/libz lib/msun \ ${_secure_lib_libcrypto} ${_lib_libldns} \ ${_secure_lib_libssh} ${_secure_lib_libssl} \ gnu/lib/libdialog .if ${MK_GNUCXX} != "no" _prebuild_libs+= gnu/lib/libstdc++ gnu/lib/libsupc++ gnu/lib/libstdc++__L: lib/msun__L gnu/lib/libsupc++__L: gnu/lib/libstdc++__L .endif .if ${MK_LIBCPLUSPLUS} != "no" _prebuild_libs+= lib/libc++ .endif lib/libgeom__L: lib/libexpat__L lib/libkvm__L: lib/libelf__L .if ${MK_LIBTHR} != "no" _lib_libthr= lib/libthr .endif .if ${MK_RADIUS_SUPPORT} != "no" _lib_libradius= lib/libradius .endif .if ${MK_OFED} != "no" _ofed_lib= contrib/ofed/usr.lib _prebuild_libs+= contrib/ofed/usr.lib/libosmcomp _prebuild_libs+= contrib/ofed/usr.lib/libopensm _prebuild_libs+= contrib/ofed/usr.lib/libibcommon _prebuild_libs+= contrib/ofed/usr.lib/libibverbs _prebuild_libs+= contrib/ofed/usr.lib/libibumad contrib/ofed/usr.lib/libopensm__L: lib/libthr__L contrib/ofed/usr.lib/libosmcomp__L: lib/libthr__L contrib/ofed/usr.lib/libibumad__L: contrib/ofed/usr.lib/libibcommon__L .endif .if ${MK_CASPER} != "no" _lib_casper= lib/libcasper .endif lib/libpjdlog__L: lib/libutil__L lib/libcasper__L: lib/libnv__L lib/liblzma__L: lib/libthr__L _generic_libs= ${_cddl_lib} gnu/lib ${_kerberos5_lib} lib ${_secure_lib} usr.bin/lex/lib ${_ofed_lib} .for _DIR in ${LOCAL_LIB_DIRS} .if exists(${.CURDIR}/${_DIR}/Makefile) && empty(_generic_libs:M${_DIR}) _generic_libs+= ${_DIR} .endif .endfor lib/libopie__L lib/libtacplus__L: lib/libmd__L .if ${MK_CDDL} != "no" _cddl_lib_libumem= cddl/lib/libumem _cddl_lib_libnvpair= cddl/lib/libnvpair _cddl_lib_libavl= cddl/lib/libavl _cddl_lib_libuutil= cddl/lib/libuutil _cddl_lib_libzfs_core= cddl/lib/libzfs_core _cddl_lib_libctf= cddl/lib/libctf _cddl_lib= cddl/lib cddl/lib/libzfs_core__L: cddl/lib/libnvpair__L cddl/lib/libzfs__L: lib/libgeom__L cddl/lib/libctf__L: lib/libz__L .endif # cddl/lib/libdtrace requires lib/libproc and lib/librtld_db; it's only built # on select architectures though (see cddl/lib/Makefile) .if ${MACHINE_CPUARCH} != "sparc64" _prebuild_libs+= lib/libproc lib/librtld_db .endif .if ${MK_CRYPT} != "no" .if ${MK_OPENSSL} != "no" _secure_lib_libcrypto= secure/lib/libcrypto _secure_lib_libssl= secure/lib/libssl lib/libradius__L secure/lib/libssl__L: secure/lib/libcrypto__L .if ${MK_LDNS} != "no" _lib_libldns= lib/libldns lib/libldns__L: secure/lib/libcrypto__L .endif .if ${MK_OPENSSH} != "no" _secure_lib_libssh= secure/lib/libssh secure/lib/libssh__L: lib/libz__L secure/lib/libcrypto__L lib/libcrypt__L .if ${MK_LDNS} != "no" secure/lib/libssh__L: lib/libldns__L .endif .if ${MK_KERBEROS_SUPPORT} != "no" secure/lib/libssh__L: lib/libgssapi__L kerberos5/lib/libkrb5__L \ kerberos5/lib/libhx509__L kerberos5/lib/libasn1__L lib/libcom_err__L \ lib/libmd__L kerberos5/lib/libroken__L .endif .endif .endif _secure_lib= secure/lib .endif .if ${MK_KERBEROS} != "no" kerberos5/lib/libasn1__L: lib/libcom_err__L kerberos5/lib/libroken__L kerberos5/lib/libhdb__L: kerberos5/lib/libasn1__L lib/libcom_err__L \ kerberos5/lib/libkrb5__L kerberos5/lib/libroken__L \ kerberos5/lib/libwind__L lib/libsqlite3__L kerberos5/lib/libheimntlm__L: secure/lib/libcrypto__L kerberos5/lib/libkrb5__L \ kerberos5/lib/libroken__L lib/libcom_err__L kerberos5/lib/libhx509__L: kerberos5/lib/libasn1__L lib/libcom_err__L \ secure/lib/libcrypto__L kerberos5/lib/libroken__L kerberos5/lib/libwind__L kerberos5/lib/libkrb5__L: kerberos5/lib/libasn1__L lib/libcom_err__L \ lib/libcrypt__L secure/lib/libcrypto__L kerberos5/lib/libhx509__L \ kerberos5/lib/libroken__L kerberos5/lib/libwind__L \ kerberos5/lib/libheimbase__L kerberos5/lib/libheimipcc__L kerberos5/lib/libroken__L: lib/libcrypt__L kerberos5/lib/libwind__L: kerberos5/lib/libroken__L lib/libcom_err__L kerberos5/lib/libheimbase__L: lib/libthr__L kerberos5/lib/libheimipcc__L: kerberos5/lib/libroken__L kerberos5/lib/libheimbase__L lib/libthr__L .endif lib/libsqlite3__L: lib/libthr__L .if ${MK_GSSAPI} != "no" _lib_libgssapi= lib/libgssapi .endif .if ${MK_KERBEROS} != "no" _kerberos5_lib= kerberos5/lib _kerberos5_lib_libasn1= kerberos5/lib/libasn1 _kerberos5_lib_libhdb= kerberos5/lib/libhdb _kerberos5_lib_libheimbase= kerberos5/lib/libheimbase _kerberos5_lib_libkrb5= kerberos5/lib/libkrb5 _kerberos5_lib_libhx509= kerberos5/lib/libhx509 _kerberos5_lib_libroken= kerberos5/lib/libroken _kerberos5_lib_libheimntlm= kerberos5/lib/libheimntlm _libsqlite3= lib/libsqlite3 _kerberos5_lib_libheimipcc= kerberos5/lib/libheimipcc _kerberos5_lib_libwind= kerberos5/lib/libwind _libcom_err= lib/libcom_err .endif .if ${MK_NIS} != "no" _lib_libypclnt= lib/libypclnt .endif .if ${MK_OPENSSL} == "no" lib/libradius__L: lib/libmd__L .endif lib/libproc__L: \ ${_cddl_lib_libctf:D${_cddl_lib_libctf}__L} lib/libelf__L lib/librtld_db__L lib/libutil__L .if ${MK_CXX} != "no" .if ${MK_LIBCPLUSPLUS} != "no" lib/libproc__L: lib/libcxxrt__L .else # This implies MK_GNUCXX != "no"; see lib/libproc lib/libproc__L: gnu/lib/libsupc++__L .endif .endif gnu/lib/libdialog__L: lib/msun__L lib/ncurses/ncursesw__L .for _lib in ${_prereq_libs} ${_lib}__PL: .PHONY .MAKE .if exists(${.CURDIR}/${_lib}) ${_+_}@${ECHODIR} "===> ${_lib} (obj,all,install)"; \ cd ${.CURDIR}/${_lib}; \ ${MAKE} MK_TESTS=no DIRPRFX=${_lib}/ obj; \ ${MAKE} MK_TESTS=no MK_PROFILE=no -DNO_PIC \ DIRPRFX=${_lib}/ all; \ ${MAKE} MK_TESTS=no MK_PROFILE=no -DNO_PIC \ DIRPRFX=${_lib}/ install .endif .endfor .for _lib in ${_startup_libs} ${_prebuild_libs:Nlib/libpam} ${_generic_libs} ${_lib}__L: .PHONY .MAKE .if exists(${.CURDIR}/${_lib}) ${_+_}@${ECHODIR} "===> ${_lib} (obj,all,install)"; \ cd ${.CURDIR}/${_lib}; \ ${MAKE} MK_TESTS=no DIRPRFX=${_lib}/ obj; \ ${MAKE} MK_TESTS=no DIRPRFX=${_lib}/ all; \ ${MAKE} MK_TESTS=no DIRPRFX=${_lib}/ install .endif .endfor # libpam is special: we need to build static PAM modules before # static PAM library, and dynamic PAM library before dynamic PAM # modules. lib/libpam__L: .PHONY .MAKE ${_+_}@${ECHODIR} "===> lib/libpam (obj,all,install)"; \ cd ${.CURDIR}/lib/libpam; \ ${MAKE} MK_TESTS=no DIRPRFX=lib/libpam/ obj; \ ${MAKE} MK_TESTS=no DIRPRFX=lib/libpam/ \ -D_NO_LIBPAM_SO_YET all; \ ${MAKE} MK_TESTS=no DIRPRFX=lib/libpam/ \ -D_NO_LIBPAM_SO_YET install _prereq_libs: ${_prereq_libs:S/$/__PL/} _startup_libs: ${_startup_libs:S/$/__L/} _prebuild_libs: ${_prebuild_libs:S/$/__L/} _generic_libs: ${_generic_libs:S/$/__L/} # Enable SUBDIR_PARALLEL when not calling 'make all', unless called from # 'everything' with _PARALLEL_SUBDIR_OK set. This is because it is unlikely # that running 'make all' from the top-level, especially with a SUBDIR_OVERRIDE # or LOCAL_DIRS set, will have a reliable build if SUBDIRs are built in # parallel. This is safe for the world stage of buildworld though since it has # already built libraries in a proper order and installed includes into # WORLDTMP. Special handling is done for SUBDIR ordering for 'install*' to # avoid trashing a system if it crashes mid-install. .if !make(all) || defined(_PARALLEL_SUBDIR_OK) SUBDIR_PARALLEL= .endif .include .if make(check-old) || make(check-old-dirs) || \ make(check-old-files) || make(check-old-libs) || \ make(delete-old) || make(delete-old-dirs) || \ make(delete-old-files) || make(delete-old-libs) # # check for / delete old files section # .include "ObsoleteFiles.inc" OLD_LIBS_MESSAGE="Please be sure no application still uses those libraries, \ else you can not start such an application. Consult UPDATING for more \ information regarding how to cope with the removal/revision bump of a \ specific library." .if !defined(BATCH_DELETE_OLD_FILES) RM_I=-i .else RM_I=-v .endif delete-old-files: @echo ">>> Removing old files (only deletes safe to delete libs)" # Ask for every old file if the user really wants to remove it. # It's annoying, but better safe than sorry. # NB: We cannot pass the list of OLD_FILES as a parameter because the # argument list will get too long. Using .for/.endfor make "loops" will make # the Makefile parser segfault. @exec 3<&0; \ cd ${.CURDIR}; \ ${MAKE} -f ${.CURDIR}/Makefile.inc1 ${.MAKEFLAGS} ${.TARGET} \ -V OLD_FILES -V "OLD_FILES:Musr/share/*.gz:R" | xargs -n1 | \ while read file; do \ if [ -f "${DESTDIR}/$${file}" -o -L "${DESTDIR}/$${file}" ]; then \ chflags noschg "${DESTDIR}/$${file}" 2>/dev/null || true; \ rm ${RM_I} "${DESTDIR}/$${file}" <&3; \ fi; \ for ext in debug symbols; do \ if ! [ -e "${DESTDIR}/$${file}" ] && [ -f \ "${DESTDIR}${DEBUGDIR}/$${file}.$${ext}" ]; then \ rm ${RM_I} "${DESTDIR}${DEBUGDIR}/$${file}.$${ext}" \ <&3; \ fi; \ done; \ done # Remove catpages without corresponding manpages. @exec 3<&0; \ find ${DESTDIR}/usr/share/man/cat* ! -type d | \ sed -ep -e's:${DESTDIR}/usr/share/man/cat:${DESTDIR}/usr/share/man/man:' | \ while read catpage; do \ read manpage; \ if [ ! -e "$${manpage}" ]; then \ rm ${RM_I} $${catpage} <&3; \ fi; \ done @echo ">>> Old files removed" check-old-files: @echo ">>> Checking for old files" @cd ${.CURDIR}; \ ${MAKE} -f ${.CURDIR}/Makefile.inc1 ${.MAKEFLAGS} ${.TARGET} \ -V OLD_FILES -V "OLD_FILES:Musr/share/*.gz:R" | xargs -n1 | \ while read file; do \ if [ -f "${DESTDIR}/$${file}" -o -L "${DESTDIR}/$${file}" ]; then \ echo "${DESTDIR}/$${file}"; \ fi; \ for ext in debug symbols; do \ if [ -f "${DESTDIR}${DEBUGDIR}/$${file}.$${ext}" ]; then \ echo "${DESTDIR}${DEBUGDIR}/$${file}.$${ext}"; \ fi; \ done; \ done # Check for catpages without corresponding manpages. @find ${DESTDIR}/usr/share/man/cat* ! -type d | \ sed -ep -e's:${DESTDIR}/usr/share/man/cat:${DESTDIR}/usr/share/man/man:' | \ while read catpage; do \ read manpage; \ if [ ! -e "$${manpage}" ]; then \ echo $${catpage}; \ fi; \ done delete-old-libs: @echo ">>> Removing old libraries" @echo "${OLD_LIBS_MESSAGE}" | fmt @exec 3<&0; \ cd ${.CURDIR}; \ ${MAKE} -f ${.CURDIR}/Makefile.inc1 ${.MAKEFLAGS} ${.TARGET} \ -V OLD_LIBS | xargs -n1 | \ while read file; do \ if [ -f "${DESTDIR}/$${file}" -o -L "${DESTDIR}/$${file}" ]; then \ chflags noschg "${DESTDIR}/$${file}" 2>/dev/null || true; \ rm ${RM_I} "${DESTDIR}/$${file}" <&3; \ fi; \ for ext in debug symbols; do \ if ! [ -e "${DESTDIR}/$${file}" ] && [ -f \ "${DESTDIR}${DEBUGDIR}/$${file}.$${ext}" ]; then \ rm ${RM_I} "${DESTDIR}${DEBUGDIR}/$${file}.$${ext}" \ <&3; \ fi; \ done; \ done @echo ">>> Old libraries removed" check-old-libs: @echo ">>> Checking for old libraries" @cd ${.CURDIR}; \ ${MAKE} -f ${.CURDIR}/Makefile.inc1 ${.MAKEFLAGS} ${.TARGET} \ -V OLD_LIBS | xargs -n1 | \ while read file; do \ if [ -f "${DESTDIR}/$${file}" -o -L "${DESTDIR}/$${file}" ]; then \ echo "${DESTDIR}/$${file}"; \ fi; \ for ext in debug symbols; do \ if [ -f "${DESTDIR}${DEBUGDIR}/$${file}.$${ext}" ]; then \ echo "${DESTDIR}${DEBUGDIR}/$${file}.$${ext}"; \ fi; \ done; \ done delete-old-dirs: @echo ">>> Removing old directories" @cd ${.CURDIR}; \ ${MAKE} -f ${.CURDIR}/Makefile.inc1 ${.MAKEFLAGS} ${.TARGET} \ -V OLD_DIRS | xargs -n1 | sort -r | \ while read dir; do \ if [ -d "${DESTDIR}/$${dir}" ]; then \ rmdir -v "${DESTDIR}/$${dir}" || true; \ elif [ -L "${DESTDIR}/$${dir}" ]; then \ echo "${DESTDIR}/$${dir} is a link, please remove everything manually."; \ fi; \ done @echo ">>> Old directories removed" check-old-dirs: @echo ">>> Checking for old directories" @cd ${.CURDIR}; \ ${MAKE} -f ${.CURDIR}/Makefile.inc1 ${.MAKEFLAGS} ${.TARGET} \ -V OLD_DIRS | xargs -n1 | \ while read dir; do \ if [ -d "${DESTDIR}/$${dir}" ]; then \ echo "${DESTDIR}/$${dir}"; \ elif [ -L "${DESTDIR}/$${dir}" ]; then \ echo "${DESTDIR}/$${dir} is a link, please remove everything manually."; \ fi; \ done delete-old: delete-old-files delete-old-dirs @echo "To remove old libraries run '${MAKE} delete-old-libs'." check-old: check-old-files check-old-libs check-old-dirs @echo "To remove old files and directories run '${MAKE} delete-old'." @echo "To remove old libraries run '${MAKE} delete-old-libs'." .endif # # showconfig - show build configuration. # showconfig: @(${MAKE} -n -f ${.CURDIR}/sys/conf/kern.opts.mk -V dummy -dg1; \ ${MAKE} -n -f ${.CURDIR}/share/mk/src.opts.mk -V dummy -dg1) 2>&1 | grep ^MK_ | sort -u .if !empty(KRNLOBJDIR) && !empty(KERNCONF) DTBOUTPUTPATH= ${KRNLOBJDIR}/${KERNCONF}/ .if !defined(FDT_DTS_FILE) || empty(FDT_DTS_FILE) .if exists(${KERNCONFDIR}/${KERNCONF}) FDT_DTS_FILE!= awk 'BEGIN {FS="="} /^makeoptions[[:space:]]+FDT_DTS_FILE/ {print $$2}' \ '${KERNCONFDIR}/${KERNCONF}' ; echo .endif .endif .endif .if !defined(DTBOUTPUTPATH) || !exists(${DTBOUTPUTPATH}) DTBOUTPUTPATH= ${.CURDIR} .endif # # Build 'standalone' Device Tree Blob # builddtb: @PATH=${TMPPATH} MACHINE=${TARGET} \ ${.CURDIR}/sys/tools/fdt/make_dtb.sh ${.CURDIR}/sys \ "${FDT_DTS_FILE}" ${DTBOUTPUTPATH} ############### # cleanworld # In the following, the first 'rm' in a series will usually remove all # files and directories. If it does not, then there are probably some # files with file flags set, so this unsets them and tries the 'rm' a # second time. There are situations where this target will be cleaning # some directories via more than one method, but that duplication is # needed to correctly handle all the possible situations. Removing all # files without file flags set in the first 'rm' instance saves time, # because 'chflags' will need to operate on fewer files afterwards. # # It is expected that BW_CANONICALOBJDIR == the CANONICALOBJDIR as would be # created by bsd.obj.mk, except that we don't want to .include that file # in this makefile. # BW_CANONICALOBJDIR:=${OBJTREE}${.CURDIR} cleanworld: .PHONY .if exists(${BW_CANONICALOBJDIR}/) -rm -rf ${BW_CANONICALOBJDIR}/* -chflags -R 0 ${BW_CANONICALOBJDIR} rm -rf ${BW_CANONICALOBJDIR}/* .endif .if ${.CURDIR} == ${.OBJDIR} || ${.CURDIR}/obj == ${.OBJDIR} # To be safe in this case, fall back to a 'make cleandir' ${_+_}@cd ${.CURDIR}; ${MAKE} cleandir .endif .if defined(TARGET) && defined(TARGET_ARCH) .if ${TARGET} == ${MACHINE} && ${TARGET_ARCH} == ${MACHINE_ARCH} XDEV_CPUTYPE?=${CPUTYPE} .else XDEV_CPUTYPE?=${TARGET_CPUTYPE} .endif NOFUN=-DNO_FSCHG MK_HTML=no -DNO_LINT \ MK_MAN=no MK_NLS=no MK_PROFILE=no \ MK_KERBEROS=no MK_RESCUE=no MK_TESTS=no MK_WARNS=no \ TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH} \ CPUTYPE=${XDEV_CPUTYPE} XDDIR=${TARGET_ARCH}-freebsd XDTP?=/usr/${XDDIR} .if ${XDTP:N/*} .error XDTP variable should be an absolute path .endif CDBENV=MAKEOBJDIRPREFIX=${MAKEOBJDIRPREFIX}/${XDDIR} \ INSTALL="sh ${.CURDIR}/tools/install.sh" CDENV= ${CDBENV} \ TOOLS_PREFIX=${XDTP} CD2CFLAGS=-isystem ${XDDESTDIR}/usr/include -L${XDDESTDIR}/usr/lib \ --sysroot=${XDDESTDIR}/ -B${XDDESTDIR}/usr/libexec \ -B${XDDESTDIR}/usr/bin -B${XDDESTDIR}/usr/lib CD2ENV=${CDENV} CC="${CC} ${CD2CFLAGS}" CXX="${CXX} ${CD2CFLAGS}" \ CPP="${CPP} ${CD2CFLAGS}" \ MACHINE=${TARGET} MACHINE_ARCH=${TARGET_ARCH} CDTMP= ${MAKEOBJDIRPREFIX}/${XDDIR}/${.CURDIR}/tmp CDMAKE=${CDENV} PATH=${CDTMP}/usr/bin:${PATH} ${MAKE} ${NOFUN} CD2MAKE=${CD2ENV} PATH=${CDTMP}/usr/bin:${XDDESTDIR}/usr/bin:${PATH} ${MAKE} ${NOFUN} XDDESTDIR=${DESTDIR}/${XDTP} .if !defined(OSREL) OSREL!= uname -r | sed -e 's/[-(].*//' .endif .ORDER: xdev-build xdev-install xdev-links xdev: xdev-build xdev-install .ORDER: _xb-worldtmp _xb-bootstrap-tools _xb-build-tools _xb-cross-tools xdev-build: _xb-worldtmp _xb-bootstrap-tools _xb-build-tools _xb-cross-tools _xb-worldtmp: .PHONY mkdir -p ${CDTMP}/usr mtree -deU -f ${.CURDIR}/etc/mtree/BSD.usr.dist \ -p ${CDTMP}/usr >/dev/null _xb-bootstrap-tools: .PHONY .for _tool in \ ${_clang_tblgen} \ ${_gperf} ${_+_}@${ECHODIR} "===> ${_tool} (obj,all,install)"; \ cd ${.CURDIR}/${_tool}; \ ${CDMAKE} DIRPRFX=${_tool}/ obj; \ ${CDMAKE} DIRPRFX=${_tool}/ all; \ ${CDMAKE} DIRPRFX=${_tool}/ DESTDIR=${CDTMP} install .endfor _xb-build-tools: .PHONY ${_+_}@cd ${.CURDIR}; \ ${CDBENV} ${MAKE} -f Makefile.inc1 ${NOFUN} build-tools _xb-cross-tools: .PHONY .for _tool in \ ${_binutils} \ ${_elftctools} \ usr.bin/ar \ ${_clang_libs} \ ${_clang} \ ${_cc} ${_+_}@${ECHODIR} "===> xdev ${_tool} (obj,all)"; \ cd ${.CURDIR}/${_tool}; \ ${CDMAKE} DIRPRFX=${_tool}/ obj; \ ${CDMAKE} DIRPRFX=${_tool}/ all .endfor _xi-mtree: .PHONY ${_+_}@${ECHODIR} "mtree populating ${XDDESTDIR}" mkdir -p ${XDDESTDIR} mtree -deU -f ${.CURDIR}/etc/mtree/BSD.root.dist \ -p ${XDDESTDIR} >/dev/null mtree -deU -f ${.CURDIR}/etc/mtree/BSD.usr.dist \ -p ${XDDESTDIR}/usr >/dev/null mtree -deU -f ${.CURDIR}/etc/mtree/BSD.include.dist \ -p ${XDDESTDIR}/usr/include >/dev/null .if defined(LIBCOMPAT) mtree -deU -f ${.CURDIR}/etc/mtree/BSD.lib${libcompat}.dist \ -p ${XDDESTDIR}/usr >/dev/null .endif .if ${MK_TESTS} != "no" mkdir -p ${XDDESTDIR}${TESTSBASE} mtree -deU -f ${.CURDIR}/etc/mtree/BSD.tests.dist \ -p ${XDDESTDIR}${TESTSBASE} >/dev/null .endif .ORDER: xdev-build _xi-mtree _xi-cross-tools _xi-includes _xi-libraries xdev-install: xdev-build _xi-mtree _xi-cross-tools _xi-includes _xi-libraries _xi-cross-tools: .PHONY @echo "_xi-cross-tools" .for _tool in \ ${_binutils} \ ${_elftctools} \ usr.bin/ar \ ${_clang_libs} \ ${_clang} \ ${_cc} ${_+_}@${ECHODIR} "===> xdev ${_tool} (install)"; \ cd ${.CURDIR}/${_tool}; \ ${CDMAKE} DIRPRFX=${_tool}/ install DESTDIR=${XDDESTDIR} .endfor _xi-includes: .PHONY ${_+_}cd ${.CURDIR}; ${CD2MAKE} -f Makefile.inc1 includes \ DESTDIR=${XDDESTDIR} _xi-libraries: .PHONY ${_+_}cd ${.CURDIR}; ${CD2MAKE} -f Makefile.inc1 libraries \ DESTDIR=${XDDESTDIR} xdev-links: .PHONY ${_+_}cd ${XDDESTDIR}/usr/bin; \ mkdir -p ../../../../usr/bin; \ for i in *; do \ ln -sf ../../${XDTP}/usr/bin/$$i \ ../../../../usr/bin/${XDDIR}-$$i; \ ln -sf ../../${XDTP}/usr/bin/$$i \ ../../../../usr/bin/${XDDIR}${OSREL}-$$i; \ done .else xdev xdev-build xdev-install xdev-links: @echo "*** Error: Both TARGET and TARGET_ARCH must be defined for \"${.TARGET}\" target" .endif Index: projects/release-pkg/Makefile.libcompat =================================================================== --- projects/release-pkg/Makefile.libcompat (revision 297604) +++ projects/release-pkg/Makefile.libcompat (revision 297605) @@ -1,171 +1,171 @@ # $FreeBSD$ .if !targets(__<${_this:T}>__) __<${_this:T}>__: # Makefile for the compatibility libraries. # - 32-bit compat libraries on PowerPC and AMD64. # could also be for mips, but that doesn't work today. # ------------------------------------------------------------------- # 32 bit world .if ${TARGET_ARCH} == "amd64" .if empty(TARGET_CPUTYPE) LIB32CPUFLAGS= -march=i686 -mmmx -msse -msse2 .else LIB32CPUFLAGS= -march=${TARGET_CPUTYPE} .endif LIB32WMAKEENV= MACHINE=i386 MACHINE_ARCH=i386 \ MACHINE_CPU="i686 mmx sse sse2" LIB32WMAKEFLAGS= \ AS="${XAS} --32" \ LD="${XLD} -m elf_i386_fbsd -Y P,${LIBCOMPATTMP}/usr/lib32" \ OBJCOPY="${XOBJCOPY}" .elif ${TARGET_ARCH} == "powerpc64" .if empty(TARGET_CPUTYPE) LIB32CPUFLAGS= -mcpu=powerpc .else LIB32CPUFLAGS= -mcpu=${TARGET_CPUTYPE} .endif LIB32WMAKEENV= MACHINE=powerpc MACHINE_ARCH=powerpc LIB32WMAKEFLAGS= \ LD="${XLD} -m elf32ppc_fbsd" \ OBJCOPY="${XOBJCOPY}" .endif LIB32CFLAGS= -m32 -DCOMPAT_32BIT LIB32DTRACE= ${DTRACE} -32 LIB32WMAKEFLAGS+= -DCOMPAT_32BIT # ------------------------------------------------------------------- # soft-fp world .if ${TARGET_ARCH} == "armv6" LIBSOFTCFLAGS= -DCOMPAT_SOFTFP LIBSOFTCPUFLAGS= -mfloat-abi=softfp LIBSOFTWMAKEENV= CPUTYPE=soft MACHINE=arm MACHINE_ARCH=armv6 LIBSOFTWMAKEFLAGS= -DCOMPAT_SOFTFP .endif # ------------------------------------------------------------------- # Generic code for each type. # Set defaults based on type. libcompat= ${LIBCOMPAT:tl} _LIBCOMPAT_MAKEVARS= _OBJTREE TMP CPUFLAGS CFLAGS CXXFLAGS WMAKEENV \ WMAKEFLAGS WMAKE .for _var in ${_LIBCOMPAT_MAKEVARS} .if !empty(LIB${LIBCOMPAT}${_var}) LIBCOMPAT${_var}?= ${LIB${LIBCOMPAT}${_var}} .endif .endfor # Shared flags LIBCOMPAT_OBJTREE?= ${OBJTREE}${.CURDIR}/world${libcompat} LIBCOMPATTMP?= ${OBJTREE}${.CURDIR}/lib${libcompat} LIBCOMPATCFLAGS+= ${LIBCOMPATCPUFLAGS} \ -L${LIBCOMPATTMP}/usr/lib${libcompat} \ --sysroot=${LIBCOMPATTMP} \ ${BFLAGS} # -B is needed to find /usr/lib32/crti.o for GCC and /usr/libsoft/crti.o for # Clang/GCC. LIBCOMPATCFLAGS+= -B${LIBCOMPATTMP}/usr/lib${libcompat} # GCC requires -isystem when using a cross-compiler. LIBCOMPATCFLAGS+= -isystem ${LIBCOMPATTMP}/usr/include .if defined(X_COMPILER_TYPE) && ${X_COMPILER_TYPE} == gcc # Force using libc++ for external GCC. LIBCOMPATCXXFLAGS+= -isystem ${LIBCOMPATTMP}/usr/include/c++/v1 -std=c++11 \ -nostdinc++ -L${LIBCOMPAT_OBJTREE}${.CURDIR}/lib/libc++ .endif # Yes, the flags are redundant. LIBCOMPATWMAKEENV+= MAKEOBJDIRPREFIX=${LIBCOMPAT_OBJTREE} \ INSTALL="sh ${.CURDIR}/tools/install.sh" \ PATH=${TMPPATH} \ LIBDIR=/usr/lib${libcompat} \ SHLIBDIR=/usr/lib${libcompat} \ DTRACE="${LIB$COMPATDTRACE:U${DTRACE}}" LIBCOMPATWMAKEFLAGS+= CC="${XCC} ${LIBCOMPATCFLAGS}" \ CXX="${XCXX} ${LIBCOMPATCFLAGS} ${LIBCOMPATCXXFLAGS}" \ DESTDIR=${LIBCOMPATTMP} \ -DNO_CPU_CFLAGS \ MK_CTF=no \ -DNO_LINT \ MK_TESTS=no LIBCOMPATWMAKE+= ${LIBCOMPATWMAKEENV} ${MAKE} ${LIBCOMPATWMAKEFLAGS} \ MK_MAN=no MK_HTML=no -LIBCOMPATIMAKE+= ${LIBCOMPATWMAKE:NINSTALL=*:NDESTDIR=*:N_LDSCRIPTROOT=*} \ +LIBCOMPATIMAKE+= ${LIBCOMPATWMAKE:NINSTALL=*:NDESTDIR=*} \ MK_TOOLCHAIN=no ${IMAKE_INSTALL} \ -DLIBRARIES_ONLY _LC_LIBDIRS.yes= lib gnu/lib _LC_LIBDIRS.${MK_CDDL:tl}+= cddl/lib _LC_LIBDIRS.${MK_CRYPT:tl}+= secure/lib _LC_LIBDIRS.${MK_KERBEROS:tl}+= kerberos5/lib _LC_INCDIRS= \ include \ lib/ncurses/ncursesw \ ${_LC_LIBDIRS.yes} # Shared logic build${libcompat}: .PHONY @echo @echo "--------------------------------------------------------------" @echo ">>> stage 5.1: building lib${libcompat} shim libraries" @echo "--------------------------------------------------------------" mkdir -p ${LIBCOMPATTMP}/usr/include mtree -deU -f ${.CURDIR}/etc/mtree/BSD.usr.dist \ -p ${LIBCOMPATTMP}/usr >/dev/null mtree -deU -f ${.CURDIR}/etc/mtree/BSD.include.dist \ -p ${LIBCOMPATTMP}/usr/include >/dev/null mtree -deU -f ${.CURDIR}/etc/mtree/BSD.lib${libcompat}.dist \ -p ${LIBCOMPATTMP}/usr >/dev/null .if ${MK_DEBUG_FILES} != "no" mtree -deU -f ${.CURDIR}/etc/mtree/BSD.debug.dist \ -p ${LIBCOMPATTMP}/usr/lib >/dev/null mtree -deU -f ${.CURDIR}/etc/mtree/BSD.lib${libcompat}.dist \ -p ${LIBCOMPATTMP}/usr/lib/debug/usr >/dev/null .endif mkdir -p ${WORLDTMP} ln -sf ${.CURDIR}/sys ${WORLDTMP} .for _t in obj includes .for _dir in ${_LC_INCDIRS} ${_+_}cd ${.CURDIR}/${_dir}; ${LIBCOMPATWMAKE} MK_INCLUDES=yes \ DIRPRFX=${_dir}/ ${_t} .endfor .endfor .for _dir in lib/ncurses/ncurses lib/ncurses/ncursesw lib/libmagic ${_+_}cd ${.CURDIR}/${_dir}; \ WORLDTMP=${WORLDTMP} \ MAKEFLAGS="-m ${.CURDIR}/tools/build/mk ${.MAKEFLAGS}" \ MAKEOBJDIRPREFIX=${LIBCOMPAT_OBJTREE} ${MAKE} SSP_CFLAGS= DESTDIR= \ DIRPRFX=${_dir}/ -DNO_LINT -DNO_CPU_CFLAGS MK_WARNS=no MK_CTF=no \ build-tools .endfor ${_+_}cd ${.CURDIR}; \ ${LIBCOMPATWMAKE} -f Makefile.inc1 -DNO_FSCHG libraries .if ${libcompat} == "32" .for _t in obj all ${_+_}cd ${.CURDIR}/libexec/rtld-elf; PROG=ld-elf32.so.1 ${LIBCOMPATWMAKE} \ -DNO_FSCHG DIRPRFX=libexec/rtld-elf/ ${_t} ${_+_}cd ${.CURDIR}/usr.bin/ldd; PROG=ldd32 ${LIBCOMPATWMAKE} \ DIRPRFX=usr.bin/ldd ${_t} .endfor .endif distribute${libcompat} install${libcompat}: .PHONY .for _dir in ${_LC_LIBDIRS.yes} ${_+_}cd ${.CURDIR}/${_dir}; ${LIBCOMPATIMAKE} ${.TARGET:S/${libcompat}$//} .endfor .if ${libcompat} == "32" ${_+_}cd ${.CURDIR}/libexec/rtld-elf; \ PROG=ld-elf32.so.1 ${LIBCOMPATIMAKE} ${.TARGET:S/32$//} ${_+_}cd ${.CURDIR}/usr.bin/ldd; PROG=ldd32 ${LIBCOMPATIMAKE} \ ${.TARGET:S/32$//} .endif .endif Index: projects/release-pkg/share/man/man4/usb_quirk.4 =================================================================== --- projects/release-pkg/share/man/man4/usb_quirk.4 (revision 297604) +++ projects/release-pkg/share/man/man4/usb_quirk.4 (revision 297605) @@ -1,253 +1,259 @@ .\" .\" Copyright (c) 2010 AnyWi Technologies .\" All rights reserved. .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" $FreeBSD$ .\" -.Dd September 26, 2015 +.Dd April 4, 2016 .Dt USB_QUIRK 4 .Os .Sh NAME .Nm usb_quirk .Nd USB quirks module .Sh SYNOPSIS To compile this module into the kernel, place the following line in your kernel configuration file: .Bd -ragged -offset indent .Cd "device usb" .Ed .Pp Alternatively, to load the module at boot time, place the following line in .Xr loader.conf 5 : .Bd -literal -offset indent usb_quirk_load="YES" .Ed .Sh DESCRIPTION The .Nm module provides support for dynamically adding and removing quirks for USB devices with .Xr usbconfig 8 . .Sh General quirks: .Bl -tag -width Ds .It UQ_AUDIO_SWAP_LR swap left and right channels .It UQ_AU_INP_ASYNC input is async despite claim of adaptive .It UQ_AU_NO_FRAC do not adjust for fractional samples .It UQ_AU_NO_XU audio device has broken extension unit .It UQ_BAD_ADC bad audio spec version number .It UQ_BAD_AUDIO device claims audio class, but is not .It UQ_BROKEN_BIDIR printer has broken bidir mode .It UQ_BUS_POWERED device is bus powered, despite claim .It UQ_HID_IGNORE device should be ignored by hid class .It UQ_KBD_IGNORE device should be ignored by kbd class .It UQ_KBD_BOOTPROTO device should set the boot protocol .It UQ_UMS_IGNORE device should be ignored by ums class .It UQ_MS_BAD_CLASS does not identify properly .It UQ_MS_LEADING_BYTE mouse sends an unknown leading byte .It UQ_MS_REVZ mouse has Z-axis reversed .It UQ_NO_STRINGS string descriptors are broken .It UQ_POWER_CLAIM hub lies about power status .It UQ_SPUR_BUT_UP spurious mouse button up events .It UQ_SWAP_UNICODE has some Unicode strings swapped .It UQ_CFG_INDEX_1 select configuration index 1 by default .It UQ_CFG_INDEX_2 select configuration index 2 by default .It UQ_CFG_INDEX_3 select configuration index 3 by default .It UQ_CFG_INDEX_4 select configuration index 4 by default .It UQ_CFG_INDEX_0 select configuration index 0 by default .It UQ_ASSUME_CM_OVER_DATA assume cm over data feature .El .Sh USB Mass Storage quirks: .Bl -tag -width Ds .It UQ_MSC_NO_TEST_UNIT_READY send start/stop instead of TUR .It UQ_MSC_NO_RS_CLEAR_UA does not reset Unit Att. .It UQ_MSC_NO_START_STOP does not support start/stop .It UQ_MSC_NO_GETMAXLUN does not support get max LUN .It UQ_MSC_NO_INQUIRY fake generic inq response .It UQ_MSC_NO_INQUIRY_EVPD does not support inq EVPD .It UQ_MSC_NO_SYNC_CACHE does not support sync cache .It UQ_MSC_SHUTTLE_INIT requires Shuttle init sequence .It UQ_MSC_ALT_IFACE_1 switch to alternate interface 1 .It UQ_MSC_FLOPPY_SPEED does floppy speeds (20kb/s) .It UQ_MSC_IGNORE_RESIDUE gets residue wrong .It UQ_MSC_WRONG_CSWSIG uses wrong CSW signature .It UQ_MSC_RBC_PAD_TO_12 pad RBC requests to 12 bytes .It UQ_MSC_READ_CAP_OFFBY1 reports sector count, not max sec. .It UQ_MSC_FORCE_SHORT_INQ does not support full inq. .It UQ_MSC_FORCE_WIRE_BBB force BBB wire protocol .It UQ_MSC_FORCE_WIRE_CBI force CBI wire protocol .It UQ_MSC_FORCE_WIRE_CBI_I force CBI with int. wire protocol .It UQ_MSC_FORCE_PROTO_SCSI force SCSI command protocol .It UQ_MSC_FORCE_PROTO_ATAPI force ATAPI command protocol .It UQ_MSC_FORCE_PROTO_UFI force UFI command protocol .It UQ_MSC_FORCE_PROTO_RBC force RBC command protocol .El .Sh 3G Datacard (u3g) quirks: .Bl -tag -width Ds .It UQ_MSC_EJECT_HUAWEI ejects after Huawei USB command .It UQ_MSC_EJECT_SIERRA ejects after Sierra USB command .It UQ_MSC_EJECT_SCSIEJECT ejects after SCSI eject command .Dv 0x1b0000000200 .It UQ_MSC_EJECT_REZERO ejects after SCSI rezero command .Dv 0x010000000000 .It UQ_MSC_EJECT_ZTESTOR ejects after ZTE SCSI command .Dv 0x850101011801010101010000 .It UQ_MSC_EJECT_CMOTECH ejects after C-motech SCSI command .Dv 0xff52444556434847 .It UQ_MSC_EJECT_WAIT wait for the device to eject .It UQ_MSC_EJECT_SAEL_M460 ejects after Sael USB commands .It UQ_MSC_EJECT_HUAWEISCSI ejects after Huawei SCSI command .Dv 0x11060000000000000000000000000000 .It UQ_MSC_EJECT_TCT ejects after TCT SCSI command .Dv 0x06f504025270 .It UQ_MSC_DYMO_EJECT ejects after HID command .Dv 0x1b5a01 .El .Pp See .Pa /sys/dev/usb/quirk/usb_quirk.h or run "usbconfig dump_quirk_names" for the complete list of supported quirks. .Sh LOADER TUNABLE The following tunable can be set at the .Xr loader 8 prompt before booting the kernel, or stored in .Xr loader.conf 5 . .Bl -tag -width indent .It Va hw.usb.quirk.%d The value is a string whose format is: .Bd -literal -offset indent .Qo VendorId ProductId LowRevision HighRevision UQ_QUIRK,... Qc .Ed .Pp Installs the quirks .Ic UQ_QUIRK,... for all USB devices matching .Ic VendorId and .Ic ProductId which have a hardware revision between and including .Ic LowRevision and .Ic HighRevision . .Pp .Ic VendorId , .Ic ProductId , .Ic LowRevision and .Ic HighRevision are all 16 bits numbers which can be decimal or hexadecimal based. .Pp A maximum of 100 variables .Ic hw.usb.quirk.0, .1, ..., .99 can be defined. .Pp If a matching entry is found in the kernel's internal quirks table, it is replaced by the new definition. .Pp Else a new entry is created given that the quirk table is not full. .Pp The kernel iterates over the .Ic hw.usb.quirk.N variables starting at .Ic N = 0 and stops at .Ic N = 99 or the first non-existing one. .El .Sh EXAMPLES After attaching a .Nm u3g device which appears as a USB device on .Pa ugen0.3 : .Bd -literal -offset indent usbconfig -d ugen0.3 add_quirk UQ_MSC_EJECT_WAIT +.Ed +.Pp +Enable a Holtec/Keep Out F85 gaming keyboard on +.Pa ugen1.4 : +.Bd -literal -offset indent +usbconfig -d ugen1.4 add_quirk UQ_KBD_BOOTPROTO .Ed .Pp To install a quirk at boot time, place one or several lines like the following in .Xr loader.conf 5 : .Bd -literal -offset indent hw.usb.quirk.0="0x04d9 0xfa50 0 0xffff UQ_KBD_IGNORE" .Ed .Sh SEE ALSO .Xr usbconfig 8 .Sh HISTORY The .Nm module appeared in .Fx 8.0 , and was written by .An Hans Petter Selasky Aq Mt hselasky@FreeBSD.org . This manual page was written by .An Nick Hibma Aq Mt n_hibma@FreeBSD.org . Index: projects/release-pkg/share/man/man4 =================================================================== --- projects/release-pkg/share/man/man4 (revision 297604) +++ projects/release-pkg/share/man/man4 (revision 297605) Property changes on: projects/release-pkg/share/man/man4 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/share/man/man4:r297567-297604 Index: projects/release-pkg/share =================================================================== --- projects/release-pkg/share (revision 297604) +++ projects/release-pkg/share (revision 297605) Property changes on: projects/release-pkg/share ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/share:r297567-297604 Index: projects/release-pkg/sys/amd64/linux/linux_sysvec.c =================================================================== --- projects/release-pkg/sys/amd64/linux/linux_sysvec.c (revision 297604) +++ projects/release-pkg/sys/amd64/linux/linux_sysvec.c (revision 297605) @@ -1,994 +1,995 @@ /*- * Copyright (c) 2013 Dmitry Chagin * Copyright (c) 2004 Tim J. Robbins * Copyright (c) 2003 Peter Wemm * Copyright (c) 2002 Doug Rabson * Copyright (c) 1998-1999 Andrew Gallatin * Copyright (c) 1994-1996 Søren Schmidt * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_compat.h" #define __ELF_WORD_SIZE 64 #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 #include #include #include MODULE_VERSION(linux64, 1); #if BYTE_ORDER == LITTLE_ENDIAN #define SHELLMAGIC 0x2123 /* #! */ #else #define SHELLMAGIC 0x2321 #endif #if defined(DEBUG) SYSCTL_PROC(_compat_linux, OID_AUTO, debug, CTLTYPE_STRING | CTLFLAG_RW, 0, 0, linux_sysctl_debug, "A", "Linux 64 debugging control"); #endif /* * Allow the this functions to use the ldebug() facility * even though they are not syscalls themselves. Map them * to syscall 0. This is slightly less bogus than using * ldebug(sigreturn). */ #define LINUX_SYS_linux_rt_sendsig 0 const char *linux_kplatform; static int linux_szsigcode; static vm_object_t linux_shared_page_obj; static char *linux_shared_page_mapping; extern char _binary_linux_locore_o_start; extern char _binary_linux_locore_o_end; extern struct sysent linux_sysent[LINUX_SYS_MAXSYSCALL]; SET_DECLARE(linux_ioctl_handler_set, struct linux_ioctl_handler); static register_t * linux_copyout_strings(struct image_params *imgp); static int elf_linux_fixup(register_t **stack_base, struct image_params *iparams); static boolean_t linux_trans_osrel(const Elf_Note *note, int32_t *osrel); static void linux_vdso_install(void *param); static void linux_vdso_deinstall(void *param); static void linux_set_syscall_retval(struct thread *td, int error); static int linux_fetch_syscall_args(struct thread *td, struct syscall_args *sa); static void linux_exec_setregs(struct thread *td, struct image_params *imgp, u_long stack); static int linux_vsyscall(struct thread *td); /* * Linux syscalls return negative errno's, we do positive and map them * Reference: * FreeBSD: src/sys/sys/errno.h * Linux: linux-2.6.17.8/include/asm-generic/errno-base.h * linux-2.6.17.8/include/asm-generic/errno.h */ static int bsd_to_linux_errno[ELAST + 1] = { -0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -35, -12, -13, -14, -15, -16, -17, -18, -19, -20, -21, -22, -23, -24, -25, -26, -27, -28, -29, -30, -31, -32, -33, -34, -11,-115,-114, -88, -89, -90, -91, -92, -93, -94, -95, -96, -97, -98, -99, -100,-101,-102,-103,-104,-105,-106,-107,-108,-109, -110,-111, -40, -36,-112,-113, -39, -11, -87,-122, -116, -66, -6, -6, -6, -6, -6, -37, -38, -9, -6, -6, -43, -42, -75,-125, -84, -95, -16, -74, -72, -67, -71 }; #define LINUX_T_UNKNOWN 255 static int _bsd_to_linux_trapcode[] = { LINUX_T_UNKNOWN, /* 0 */ 6, /* 1 T_PRIVINFLT */ LINUX_T_UNKNOWN, /* 2 */ 3, /* 3 T_BPTFLT */ LINUX_T_UNKNOWN, /* 4 */ LINUX_T_UNKNOWN, /* 5 */ 16, /* 6 T_ARITHTRAP */ 254, /* 7 T_ASTFLT */ LINUX_T_UNKNOWN, /* 8 */ 13, /* 9 T_PROTFLT */ 1, /* 10 T_TRCTRAP */ LINUX_T_UNKNOWN, /* 11 */ 14, /* 12 T_PAGEFLT */ LINUX_T_UNKNOWN, /* 13 */ 17, /* 14 T_ALIGNFLT */ LINUX_T_UNKNOWN, /* 15 */ LINUX_T_UNKNOWN, /* 16 */ LINUX_T_UNKNOWN, /* 17 */ 0, /* 18 T_DIVIDE */ 2, /* 19 T_NMI */ 4, /* 20 T_OFLOW */ 5, /* 21 T_BOUND */ 7, /* 22 T_DNA */ 8, /* 23 T_DOUBLEFLT */ 9, /* 24 T_FPOPFLT */ 10, /* 25 T_TSSFLT */ 11, /* 26 T_SEGNPFLT */ 12, /* 27 T_STKFLT */ 18, /* 28 T_MCHK */ 19, /* 29 T_XMMFLT */ 15 /* 30 T_RESERVED */ }; #define bsd_to_linux_trapcode(code) \ ((code)td_proc; frame = td->td_frame; sa->args[0] = frame->tf_rdi; sa->args[1] = frame->tf_rsi; sa->args[2] = frame->tf_rdx; sa->args[3] = frame->tf_rcx; sa->args[4] = frame->tf_r8; sa->args[5] = frame->tf_r9; sa->code = frame->tf_rax; if (sa->code >= p->p_sysent->sv_size) /* nosys */ sa->callp = &p->p_sysent->sv_table[p->p_sysent->sv_size - 1]; else sa->callp = &p->p_sysent->sv_table[sa->code]; sa->narg = sa->callp->sy_narg; td->td_retval[0] = 0; return (0); } static void linux_set_syscall_retval(struct thread *td, int error) { struct trapframe *frame = td->td_frame; /* * On Linux only %rcx and %r11 values are not preserved across * the syscall. * So, do not clobber %rdx and %r10 */ td->td_retval[1] = frame->tf_rdx; frame->tf_r10 = frame->tf_rcx; cpu_set_syscall_retval(td, error); /* Restore all registers. */ set_pcb_flags(td->td_pcb, PCB_FULL_IRET); } static int elf_linux_fixup(register_t **stack_base, struct image_params *imgp) { Elf_Auxargs *args; Elf_Addr *base; Elf_Addr *pos; struct ps_strings *arginfo; struct proc *p; int issetugid; p = imgp->proc; arginfo = (struct ps_strings *)p->p_sysent->sv_psstrings; KASSERT(curthread->td_proc == imgp->proc, ("unsafe elf_linux_fixup(), should be curproc")); base = (Elf64_Addr *)*stack_base; args = (Elf64_Auxargs *)imgp->auxargs; pos = base + (imgp->args->argc + imgp->args->envc + 2); issetugid = p->p_flag & P_SUGID ? 1 : 0; AUXARGS_ENTRY(pos, LINUX_AT_SYSINFO_EHDR, imgp->proc->p_sysent->sv_shared_page_base); AUXARGS_ENTRY(pos, LINUX_AT_HWCAP, cpu_feature); AUXARGS_ENTRY(pos, LINUX_AT_CLKTCK, stclohz); AUXARGS_ENTRY(pos, AT_PHDR, args->phdr); AUXARGS_ENTRY(pos, AT_PHENT, args->phent); AUXARGS_ENTRY(pos, AT_PHNUM, args->phnum); AUXARGS_ENTRY(pos, AT_PAGESZ, args->pagesz); AUXARGS_ENTRY(pos, AT_BASE, args->base); AUXARGS_ENTRY(pos, AT_FLAGS, args->flags); AUXARGS_ENTRY(pos, AT_ENTRY, args->entry); AUXARGS_ENTRY(pos, AT_UID, imgp->proc->p_ucred->cr_ruid); AUXARGS_ENTRY(pos, AT_EUID, imgp->proc->p_ucred->cr_svuid); AUXARGS_ENTRY(pos, AT_GID, imgp->proc->p_ucred->cr_rgid); AUXARGS_ENTRY(pos, AT_EGID, imgp->proc->p_ucred->cr_svgid); AUXARGS_ENTRY(pos, LINUX_AT_SECURE, issetugid); AUXARGS_ENTRY(pos, LINUX_AT_PLATFORM, PTROUT(linux_platform)); AUXARGS_ENTRY(pos, LINUX_AT_RANDOM, imgp->canary); if (imgp->execpathp != 0) AUXARGS_ENTRY(pos, LINUX_AT_EXECFN, imgp->execpathp); if (args->execfd != -1) AUXARGS_ENTRY(pos, AT_EXECFD, args->execfd); AUXARGS_ENTRY(pos, AT_NULL, 0); free(imgp->auxargs, M_TEMP); imgp->auxargs = NULL; base--; suword(base, (uint64_t)imgp->args->argc); *stack_base = (register_t *)base; return (0); } /* * Copy strings out to the new process address space, constructing new arg * and env vector tables. Return a pointer to the base so that it can be used * as the initial stack pointer. */ static register_t * linux_copyout_strings(struct image_params *imgp) { int argc, envc; char **vectp; char *stringp, *destp; register_t *stack_base; struct ps_strings *arginfo; char canary[LINUX_AT_RANDOM_LEN]; size_t execpath_len; struct proc *p; /* * Calculate string base and vector table pointers. */ if (imgp->execpath != NULL && imgp->auxargs != NULL) execpath_len = strlen(imgp->execpath) + 1; else execpath_len = 0; p = imgp->proc; arginfo = (struct ps_strings *)p->p_sysent->sv_psstrings; destp = (caddr_t)arginfo - SPARE_USRSPACE - roundup(sizeof(canary), sizeof(char *)) - roundup(execpath_len, sizeof(char *)) - roundup((ARG_MAX - imgp->args->stringspace), sizeof(char *)); if (execpath_len != 0) { imgp->execpathp = (uintptr_t)arginfo - execpath_len; copyout(imgp->execpath, (void *)imgp->execpathp, execpath_len); } /* * Prepare the canary for SSP. */ arc4rand(canary, sizeof(canary), 0); imgp->canary = (uintptr_t)arginfo - roundup(execpath_len, sizeof(char *)) - roundup(sizeof(canary), sizeof(char *)); copyout(canary, (void *)imgp->canary, sizeof(canary)); /* * If we have a valid auxargs ptr, prepare some room * on the stack. */ if (imgp->auxargs) { /* * 'AT_COUNT*2' is size for the ELF Auxargs data. This is for * lower compatibility. */ imgp->auxarg_size = (imgp->auxarg_size) ? imgp->auxarg_size : (LINUX_AT_COUNT * 2); /* * The '+ 2' is for the null pointers at the end of each of * the arg and env vector sets,and imgp->auxarg_size is room * for argument of Runtime loader. */ vectp = (char **)(destp - (imgp->args->argc + imgp->args->envc + 2 + imgp->auxarg_size) * sizeof(char *)); } else { /* * The '+ 2' is for the null pointers at the end of each of * the arg and env vector sets */ vectp = (char **)(destp - (imgp->args->argc + imgp->args->envc + 2) * sizeof(char *)); } /* * vectp also becomes our initial stack base */ stack_base = (register_t *)vectp; stringp = imgp->args->begin_argv; argc = imgp->args->argc; envc = imgp->args->envc; /* * Copy out strings - arguments and environment. */ copyout(stringp, destp, ARG_MAX - imgp->args->stringspace); /* * Fill in "ps_strings" struct for ps, w, etc. */ suword(&arginfo->ps_argvstr, (long)(intptr_t)vectp); suword(&arginfo->ps_nargvstr, argc); /* * Fill in argument portion of vector table. */ for (; argc > 0; --argc) { suword(vectp++, (long)(intptr_t)destp); while (*stringp++ != 0) destp++; destp++; } /* a null vector table pointer separates the argp's from the envp's */ suword(vectp++, 0); suword(&arginfo->ps_envstr, (long)(intptr_t)vectp); suword(&arginfo->ps_nenvstr, envc); /* * Fill in environment portion of vector table. */ for (; envc > 0; --envc) { suword(vectp++, (long)(intptr_t)destp); while (*stringp++ != 0) destp++; destp++; } /* end of vector table is a null pointer */ suword(vectp, 0); return (stack_base); } /* * Reset registers to default values on exec. */ static void linux_exec_setregs(struct thread *td, struct image_params *imgp, u_long stack) { struct trapframe *regs = td->td_frame; struct pcb *pcb = td->td_pcb; mtx_lock(&dt_lock); if (td->td_proc->p_md.md_ldt != NULL) user_ldt_free(td); else mtx_unlock(&dt_lock); pcb->pcb_fsbase = 0; pcb->pcb_gsbase = 0; clear_pcb_flags(pcb, PCB_32BIT); pcb->pcb_initial_fpucw = __LINUX_NPXCW__; set_pcb_flags(pcb, PCB_FULL_IRET); bzero((char *)regs, sizeof(struct trapframe)); regs->tf_rip = imgp->entry_addr; regs->tf_rsp = stack; regs->tf_rflags = PSL_USER | (regs->tf_rflags & PSL_T); regs->tf_ss = _udatasel; regs->tf_cs = _ucodesel; regs->tf_ds = _udatasel; regs->tf_es = _udatasel; regs->tf_fs = _ufssel; regs->tf_gs = _ugssel; regs->tf_flags = TF_HASSEGS; /* * Reset the hardware debug registers if they were in use. * They won't have any meaning for the newly exec'd process. */ if (pcb->pcb_flags & PCB_DBREGS) { pcb->pcb_dr0 = 0; pcb->pcb_dr1 = 0; pcb->pcb_dr2 = 0; pcb->pcb_dr3 = 0; pcb->pcb_dr6 = 0; pcb->pcb_dr7 = 0; if (pcb == curpcb) { /* * Clear the debug registers on the running * CPU, otherwise they will end up affecting * the next process we switch to. */ reset_dbregs(); } clear_pcb_flags(pcb, PCB_DBREGS); } /* * Drop the FP state if we hold it, so that the process gets a * clean FP state if it uses the FPU again. */ fpstate_drop(td); } /* * Copied from amd64/amd64/machdep.c * * XXX fpu state need? don't think so */ int linux_rt_sigreturn(struct thread *td, struct linux_rt_sigreturn_args *args) { struct proc *p; struct l_ucontext uc; struct l_sigcontext *context; struct trapframe *regs; unsigned long rflags; int error; ksiginfo_t ksi; regs = td->td_frame; error = copyin((void *)regs->tf_rbx, &uc, sizeof(uc)); if (error != 0) return (error); p = td->td_proc; context = &uc.uc_mcontext; rflags = context->sc_rflags; /* * Don't allow users to change privileged or reserved flags. */ /* * XXX do allow users to change the privileged flag PSL_RF. * The cpu sets PSL_RF in tf_rflags for faults. Debuggers * should sometimes set it there too. tf_rflags is kept in * the signal context during signal handling and there is no * other place to remember it, so the PSL_RF bit may be * corrupted by the signal handler without us knowing. * Corruption of the PSL_RF bit at worst causes one more or * one less debugger trap, so allowing it is fairly harmless. */ #define RFLAG_SECURE(ef, oef) ((((ef) ^ (oef)) & ~PSL_USERCHANGE) == 0) if (!RFLAG_SECURE(rflags & ~PSL_RF, regs->tf_rflags & ~PSL_RF)) { printf("linux_rt_sigreturn: rflags = 0x%lx\n", rflags); return (EINVAL); } /* * Don't allow users to load a valid privileged %cs. Let the * hardware check for invalid selectors, excess privilege in * other selectors, invalid %eip's and invalid %esp's. */ #define CS_SECURE(cs) (ISPL(cs) == SEL_UPL) if (!CS_SECURE(context->sc_cs)) { printf("linux_rt_sigreturn: cs = 0x%x\n", context->sc_cs); ksiginfo_init_trap(&ksi); ksi.ksi_signo = SIGBUS; ksi.ksi_code = BUS_OBJERR; ksi.ksi_trapno = T_PROTFLT; ksi.ksi_addr = (void *)regs->tf_rip; trapsignal(td, &ksi); return (EINVAL); } PROC_LOCK(p); linux_to_bsd_sigset(&uc.uc_sigmask, &td->td_sigmask); SIG_CANTMASK(td->td_sigmask); signotify(td); PROC_UNLOCK(p); regs->tf_rdi = context->sc_rdi; regs->tf_rsi = context->sc_rsi; regs->tf_rdx = context->sc_rdx; regs->tf_rbp = context->sc_rbp; regs->tf_rbx = context->sc_rbx; regs->tf_rcx = context->sc_rcx; regs->tf_rax = context->sc_rax; regs->tf_rip = context->sc_rip; regs->tf_rsp = context->sc_rsp; regs->tf_r8 = context->sc_r8; regs->tf_r9 = context->sc_r9; regs->tf_r10 = context->sc_r10; regs->tf_r11 = context->sc_r11; regs->tf_r12 = context->sc_r12; regs->tf_r13 = context->sc_r13; regs->tf_r14 = context->sc_r14; regs->tf_r15 = context->sc_r15; regs->tf_cs = context->sc_cs; regs->tf_err = context->sc_err; regs->tf_rflags = rflags; set_pcb_flags(td->td_pcb, PCB_FULL_IRET); return (EJUSTRETURN); } /* * copied from amd64/amd64/machdep.c * * Send an interrupt to process. */ static void linux_rt_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask) { struct l_rt_sigframe sf, *sfp; struct proc *p; struct thread *td; struct sigacts *psp; caddr_t sp; struct trapframe *regs; int sig, code; int oonstack; td = curthread; p = td->td_proc; PROC_LOCK_ASSERT(p, MA_OWNED); sig = ksi->ksi_signo; psp = p->p_sigacts; code = ksi->ksi_code; mtx_assert(&psp->ps_mtx, MA_OWNED); regs = td->td_frame; oonstack = sigonstack(regs->tf_rsp); LINUX_CTR4(rt_sendsig, "%p, %d, %p, %u", catcher, sig, mask, code); /* Allocate space for the signal handler context. */ if ((td->td_pflags & TDP_ALTSTACK) != 0 && !oonstack && SIGISMEMBER(psp->ps_sigonstack, sig)) { sp = (caddr_t)td->td_sigstk.ss_sp + td->td_sigstk.ss_size - sizeof(struct l_rt_sigframe); } else sp = (caddr_t)regs->tf_rsp - sizeof(struct l_rt_sigframe) - 128; /* Align to 16 bytes. */ sfp = (struct l_rt_sigframe *)((unsigned long)sp & ~0xFul); mtx_unlock(&psp->ps_mtx); /* Translate the signal. */ sig = bsd_to_linux_signal(sig); /* Save user context. */ bzero(&sf, sizeof(sf)); bsd_to_linux_sigset(mask, &sf.sf_sc.uc_sigmask); bsd_to_linux_sigset(mask, &sf.sf_sc.uc_mcontext.sc_mask); sf.sf_sc.uc_stack.ss_sp = PTROUT(td->td_sigstk.ss_sp); sf.sf_sc.uc_stack.ss_size = td->td_sigstk.ss_size; sf.sf_sc.uc_stack.ss_flags = (td->td_pflags & TDP_ALTSTACK) ? ((oonstack) ? LINUX_SS_ONSTACK : 0) : LINUX_SS_DISABLE; PROC_UNLOCK(p); sf.sf_sc.uc_mcontext.sc_rdi = regs->tf_rdi; sf.sf_sc.uc_mcontext.sc_rsi = regs->tf_rsi; sf.sf_sc.uc_mcontext.sc_rdx = regs->tf_rdx; sf.sf_sc.uc_mcontext.sc_rbp = regs->tf_rbp; sf.sf_sc.uc_mcontext.sc_rbx = regs->tf_rbx; sf.sf_sc.uc_mcontext.sc_rcx = regs->tf_rcx; sf.sf_sc.uc_mcontext.sc_rax = regs->tf_rax; sf.sf_sc.uc_mcontext.sc_rip = regs->tf_rip; sf.sf_sc.uc_mcontext.sc_rsp = regs->tf_rsp; sf.sf_sc.uc_mcontext.sc_r8 = regs->tf_r8; sf.sf_sc.uc_mcontext.sc_r9 = regs->tf_r9; sf.sf_sc.uc_mcontext.sc_r10 = regs->tf_r10; sf.sf_sc.uc_mcontext.sc_r11 = regs->tf_r11; sf.sf_sc.uc_mcontext.sc_r12 = regs->tf_r12; sf.sf_sc.uc_mcontext.sc_r13 = regs->tf_r13; sf.sf_sc.uc_mcontext.sc_r14 = regs->tf_r14; sf.sf_sc.uc_mcontext.sc_r15 = regs->tf_r15; sf.sf_sc.uc_mcontext.sc_cs = regs->tf_cs; sf.sf_sc.uc_mcontext.sc_rflags = regs->tf_rflags; sf.sf_sc.uc_mcontext.sc_err = regs->tf_err; sf.sf_sc.uc_mcontext.sc_trapno = bsd_to_linux_trapcode(code); sf.sf_sc.uc_mcontext.sc_cr2 = (register_t)ksi->ksi_addr; /* Build the argument list for the signal handler. */ regs->tf_rdi = sig; /* arg 1 in %rdi */ regs->tf_rax = 0; regs->tf_rsi = (register_t)&sfp->sf_si; /* arg 2 in %rsi */ regs->tf_rdx = (register_t)&sfp->sf_sc; /* arg 3 in %rdx */ sf.sf_handler = catcher; /* Fill in POSIX parts */ ksiginfo_to_lsiginfo(ksi, &sf.sf_si, sig); /* * Copy the sigframe out to the user's stack. */ if (copyout(&sf, sfp, sizeof(*sfp)) != 0) { #ifdef DEBUG printf("process %ld has trashed its stack\n", (long)p->p_pid); #endif PROC_LOCK(p); sigexit(td, SIGILL); } regs->tf_rsp = (long)sfp; regs->tf_rip = linux_rt_sigcode; regs->tf_rflags &= ~(PSL_T | PSL_D); regs->tf_cs = _ucodesel; set_pcb_flags(td->td_pcb, PCB_FULL_IRET); PROC_LOCK(p); mtx_lock(&psp->ps_mtx); } /* * If a linux binary is exec'ing something, try this image activator * first. We override standard shell script execution in order to * be able to modify the interpreter path. We only do this if a linux * binary is doing the exec, so we do not create an EXEC module for it. */ static int exec_linux_imgact_try(struct image_params *iparams); static int exec_linux_imgact_try(struct image_params *imgp) { const char *head = (const char *)imgp->image_header; char *rpath; int error = -1, len; /* * The interpreter for shell scripts run from a linux binary needs * to be located in /compat/linux if possible in order to recursively * maintain linux path emulation. */ if (((const short *)head)[0] == SHELLMAGIC) { /* * Run our normal shell image activator. If it succeeds * attempt to use the alternate path for the interpreter. * If an alternate path is found, use our stringspace * to store it. */ if ((error = exec_shell_imgact(imgp)) == 0) { linux_emul_convpath(FIRST_THREAD_IN_PROC(imgp->proc), imgp->interpreter_name, UIO_SYSSPACE, &rpath, 0, AT_FDCWD); if (rpath != NULL) { len = strlen(rpath) + 1; if (len <= MAXSHELLCMDLEN) memcpy(imgp->interpreter_name, rpath, len); free(rpath, M_TEMP); } } } return(error); } #define LINUX_VSYSCALL_START (-10UL << 20) #define LINUX_VSYSCALL_SZ 1024 const unsigned long linux_vsyscall_vector[] = { LINUX_SYS_gettimeofday, LINUX_SYS_linux_time, /* getcpu not implemented */ }; static int linux_vsyscall(struct thread *td) { struct trapframe *frame; uint64_t retqaddr; int code, traced; int error; frame = td->td_frame; /* Check %rip for vsyscall area */ if (__predict_true(frame->tf_rip < LINUX_VSYSCALL_START)) return (EINVAL); if ((frame->tf_rip & (LINUX_VSYSCALL_SZ - 1)) != 0) return (EINVAL); code = (frame->tf_rip - LINUX_VSYSCALL_START) / LINUX_VSYSCALL_SZ; if (code >= nitems(linux_vsyscall_vector)) return (EINVAL); /* * vsyscall called as callq *(%rax), so we must * use return address from %rsp and also fixup %rsp */ error = copyin((void *)frame->tf_rsp, &retqaddr, sizeof(retqaddr)); if (error) return (error); frame->tf_rip = retqaddr; frame->tf_rax = linux_vsyscall_vector[code]; frame->tf_rsp += 8; traced = (frame->tf_flags & PSL_T); amd64_syscall(td, traced); return (0); } struct sysentvec elf_linux_sysvec = { .sv_size = LINUX_SYS_MAXSYSCALL, .sv_table = linux_sysent, .sv_mask = 0, .sv_errsize = ELAST + 1, .sv_errtbl = bsd_to_linux_errno, .sv_transtrap = translate_traps, .sv_fixup = elf_linux_fixup, .sv_sendsig = linux_rt_sendsig, .sv_sigcode = &_binary_linux_locore_o_start, .sv_szsigcode = &linux_szsigcode, .sv_name = "Linux ELF64", .sv_coredump = elf64_coredump, .sv_imgact_try = exec_linux_imgact_try, .sv_minsigstksz = LINUX_MINSIGSTKSZ, .sv_pagesize = PAGE_SIZE, .sv_minuser = VM_MIN_ADDRESS, .sv_maxuser = VM_MAXUSER_ADDRESS, .sv_usrstack = USRSTACK, .sv_psstrings = PS_STRINGS, .sv_stackprot = VM_PROT_ALL, .sv_copyout_strings = linux_copyout_strings, .sv_setregs = linux_exec_setregs, .sv_fixlimit = NULL, .sv_maxssiz = NULL, .sv_flags = SV_ABI_LINUX | SV_LP64 | SV_SHP, .sv_set_syscall_retval = linux_set_syscall_retval, .sv_fetch_syscall_args = linux_fetch_syscall_args, .sv_syscallnames = NULL, .sv_shared_page_base = SHAREDPAGE, .sv_shared_page_len = PAGE_SIZE, .sv_schedtail = linux_schedtail, .sv_thread_detach = linux_thread_detach, .sv_trap = linux_vsyscall, }; static void linux_vdso_install(void *param) { linux_szsigcode = (&_binary_linux_locore_o_end - &_binary_linux_locore_o_start); if (linux_szsigcode > elf_linux_sysvec.sv_shared_page_len) panic("Linux invalid vdso size\n"); __elfN(linux_vdso_fixup)(&elf_linux_sysvec); linux_shared_page_obj = __elfN(linux_shared_page_init) (&linux_shared_page_mapping); __elfN(linux_vdso_reloc)(&elf_linux_sysvec, SHAREDPAGE); bcopy(elf_linux_sysvec.sv_sigcode, linux_shared_page_mapping, linux_szsigcode); elf_linux_sysvec.sv_shared_page_obj = linux_shared_page_obj; linux_kplatform = linux_shared_page_mapping + (linux_platform - (caddr_t)SHAREDPAGE); } SYSINIT(elf_linux_vdso_init, SI_SUB_EXEC, SI_ORDER_ANY, (sysinit_cfunc_t)linux_vdso_install, NULL); static void linux_vdso_deinstall(void *param) { __elfN(linux_shared_page_fini)(linux_shared_page_obj); }; SYSUNINIT(elf_linux_vdso_uninit, SI_SUB_EXEC, SI_ORDER_FIRST, (sysinit_cfunc_t)linux_vdso_deinstall, NULL); static char GNULINUX_ABI_VENDOR[] = "GNU"; static int GNULINUX_ABI_DESC = 0; static boolean_t linux_trans_osrel(const Elf_Note *note, int32_t *osrel) { const Elf32_Word *desc; uintptr_t p; p = (uintptr_t)(note + 1); p += roundup2(note->n_namesz, sizeof(Elf32_Addr)); desc = (const Elf32_Word *)p; if (desc[0] != GNULINUX_ABI_DESC) return (FALSE); /* * For linux we encode osrel as follows (see linux_mib.c): * VVVMMMIII (version, major, minor), see linux_mib.c. */ *osrel = desc[1] * 1000000 + desc[2] * 1000 + desc[3]; return (TRUE); } static Elf_Brandnote linux64_brandnote = { .hdr.n_namesz = sizeof(GNULINUX_ABI_VENDOR), .hdr.n_descsz = 16, .hdr.n_type = 1, .vendor = GNULINUX_ABI_VENDOR, .flags = BN_TRANSLATE_OSREL, .trans_osrel = linux_trans_osrel }; static Elf64_Brandinfo linux_glibc2brand = { .brand = ELFOSABI_LINUX, .machine = EM_X86_64, .compat_3_brand = "Linux", .emul_path = "/compat/linux", .interp_path = "/lib64/ld-linux-x86-64.so.2", .sysvec = &elf_linux_sysvec, .interp_newpath = NULL, .brand_note = &linux64_brandnote, .flags = BI_CAN_EXEC_DYN | BI_BRAND_NOTE }; static Elf64_Brandinfo linux_glibc2brandshort = { .brand = ELFOSABI_LINUX, .machine = EM_X86_64, .compat_3_brand = "Linux", .emul_path = "/compat/linux", .interp_path = "/lib64/ld-linux.so.2", .sysvec = &elf_linux_sysvec, .interp_newpath = NULL, .brand_note = &linux64_brandnote, .flags = BI_CAN_EXEC_DYN | BI_BRAND_NOTE }; Elf64_Brandinfo *linux_brandlist[] = { &linux_glibc2brand, &linux_glibc2brandshort, NULL }; static int linux64_elf_modevent(module_t mod, int type, void *data) { Elf64_Brandinfo **brandinfo; int error; struct linux_ioctl_handler **lihp; error = 0; switch(type) { case MOD_LOAD: for (brandinfo = &linux_brandlist[0]; *brandinfo != NULL; ++brandinfo) if (elf64_insert_brand_entry(*brandinfo) < 0) error = EINVAL; if (error == 0) { SET_FOREACH(lihp, linux_ioctl_handler_set) linux_ioctl_register_handler(*lihp); LIST_INIT(&futex_list); mtx_init(&futex_mtx, "ftllk64", NULL, MTX_DEF); stclohz = (stathz ? stathz : hz); if (bootverbose) printf("Linux x86-64 ELF exec handler installed\n"); } else printf("cannot insert Linux x86-64 ELF brand handler\n"); break; case MOD_UNLOAD: for (brandinfo = &linux_brandlist[0]; *brandinfo != NULL; ++brandinfo) if (elf64_brand_inuse(*brandinfo)) error = EBUSY; if (error == 0) { for (brandinfo = &linux_brandlist[0]; *brandinfo != NULL; ++brandinfo) if (elf64_remove_brand_entry(*brandinfo) < 0) error = EINVAL; } if (error == 0) { SET_FOREACH(lihp, linux_ioctl_handler_set) linux_ioctl_unregister_handler(*lihp); mtx_destroy(&futex_mtx); if (bootverbose) printf("Linux ELF exec handler removed\n"); } else printf("Could not deinstall ELF interpreter entry\n"); break; default: return (EOPNOTSUPP); } return (error); } static moduledata_t linux64_elf_mod = { "linux64elf", linux64_elf_modevent, 0 }; DECLARE_MODULE_TIED(linux64elf, linux64_elf_mod, SI_SUB_EXEC, SI_ORDER_ANY); MODULE_DEPEND(linux64elf, linux_common, 1, 1, 1); +FEATURE(linux64, "Linux 64bit support"); Index: projects/release-pkg/sys/amd64/linux32/linux32_sysvec.c =================================================================== --- projects/release-pkg/sys/amd64/linux32/linux32_sysvec.c (revision 297604) +++ projects/release-pkg/sys/amd64/linux32/linux32_sysvec.c (revision 297605) @@ -1,1207 +1,1208 @@ /*- * Copyright (c) 2004 Tim J. Robbins * Copyright (c) 2003 Peter Wemm * Copyright (c) 2002 Doug Rabson * Copyright (c) 1998-1999 Andrew Gallatin * Copyright (c) 1994-1996 Søren Schmidt * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_compat.h" #ifndef COMPAT_FREEBSD32 #error "Unable to compile Linux-emulator due to missing COMPAT_FREEBSD32 option!" #endif #define __ELF_WORD_SIZE 32 #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 #include MODULE_VERSION(linux, 1); #define AUXARGS_ENTRY_32(pos, id, val) \ do { \ suword32(pos++, id); \ suword32(pos++, val); \ } while (0) #if BYTE_ORDER == LITTLE_ENDIAN #define SHELLMAGIC 0x2123 /* #! */ #else #define SHELLMAGIC 0x2321 #endif /* * Allow the sendsig functions to use the ldebug() facility * even though they are not syscalls themselves. Map them * to syscall 0. This is slightly less bogus than using * ldebug(sigreturn). */ #define LINUX32_SYS_linux_rt_sendsig 0 #define LINUX32_SYS_linux_sendsig 0 const char *linux_kplatform; static int linux_szsigcode; static vm_object_t linux_shared_page_obj; static char *linux_shared_page_mapping; extern char _binary_linux32_locore_o_start; extern char _binary_linux32_locore_o_end; extern struct sysent linux32_sysent[LINUX32_SYS_MAXSYSCALL]; SET_DECLARE(linux_ioctl_handler_set, struct linux_ioctl_handler); static int elf_linux_fixup(register_t **stack_base, struct image_params *iparams); static register_t *linux_copyout_strings(struct image_params *imgp); static void linux_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask); static void exec_linux_setregs(struct thread *td, struct image_params *imgp, u_long stack); static void linux32_fixlimit(struct rlimit *rl, int which); static boolean_t linux32_trans_osrel(const Elf_Note *note, int32_t *osrel); static void linux_vdso_install(void *param); static void linux_vdso_deinstall(void *param); /* * Linux syscalls return negative errno's, we do positive and map them * Reference: * FreeBSD: src/sys/sys/errno.h * Linux: linux-2.6.17.8/include/asm-generic/errno-base.h * linux-2.6.17.8/include/asm-generic/errno.h */ static int bsd_to_linux_errno[ELAST + 1] = { -0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -35, -12, -13, -14, -15, -16, -17, -18, -19, -20, -21, -22, -23, -24, -25, -26, -27, -28, -29, -30, -31, -32, -33, -34, -11,-115,-114, -88, -89, -90, -91, -92, -93, -94, -95, -96, -97, -98, -99, -100,-101,-102,-103,-104,-105,-106,-107,-108,-109, -110,-111, -40, -36,-112,-113, -39, -11, -87,-122, -116, -66, -6, -6, -6, -6, -6, -37, -38, -9, -6, -6, -43, -42, -75,-125, -84, -95, -16, -74, -72, -67, -71 }; #define LINUX_T_UNKNOWN 255 static int _bsd_to_linux_trapcode[] = { LINUX_T_UNKNOWN, /* 0 */ 6, /* 1 T_PRIVINFLT */ LINUX_T_UNKNOWN, /* 2 */ 3, /* 3 T_BPTFLT */ LINUX_T_UNKNOWN, /* 4 */ LINUX_T_UNKNOWN, /* 5 */ 16, /* 6 T_ARITHTRAP */ 254, /* 7 T_ASTFLT */ LINUX_T_UNKNOWN, /* 8 */ 13, /* 9 T_PROTFLT */ 1, /* 10 T_TRCTRAP */ LINUX_T_UNKNOWN, /* 11 */ 14, /* 12 T_PAGEFLT */ LINUX_T_UNKNOWN, /* 13 */ 17, /* 14 T_ALIGNFLT */ LINUX_T_UNKNOWN, /* 15 */ LINUX_T_UNKNOWN, /* 16 */ LINUX_T_UNKNOWN, /* 17 */ 0, /* 18 T_DIVIDE */ 2, /* 19 T_NMI */ 4, /* 20 T_OFLOW */ 5, /* 21 T_BOUND */ 7, /* 22 T_DNA */ 8, /* 23 T_DOUBLEFLT */ 9, /* 24 T_FPOPFLT */ 10, /* 25 T_TSSFLT */ 11, /* 26 T_SEGNPFLT */ 12, /* 27 T_STKFLT */ 18, /* 28 T_MCHK */ 19, /* 29 T_XMMFLT */ 15 /* 30 T_RESERVED */ }; #define bsd_to_linux_trapcode(code) \ ((code)td_proc == imgp->proc, ("unsafe elf_linux_fixup(), should be curproc")); base = (Elf32_Addr *)*stack_base; args = (Elf32_Auxargs *)imgp->auxargs; pos = base + (imgp->args->argc + imgp->args->envc + 2); issetugid = imgp->proc->p_flag & P_SUGID ? 1 : 0; AUXARGS_ENTRY_32(pos, LINUX_AT_SYSINFO_EHDR, imgp->proc->p_sysent->sv_shared_page_base); AUXARGS_ENTRY_32(pos, LINUX_AT_SYSINFO, linux32_vsyscall); AUXARGS_ENTRY_32(pos, LINUX_AT_HWCAP, cpu_feature); /* * Do not export AT_CLKTCK when emulating Linux kernel prior to 2.4.0, * as it has appeared in the 2.4.0-rc7 first time. * Being exported, AT_CLKTCK is returned by sysconf(_SC_CLK_TCK), * glibc falls back to the hard-coded CLK_TCK value when aux entry * is not present. * Also see linux_times() implementation. */ if (linux_kernver(curthread) >= LINUX_KERNVER_2004000) AUXARGS_ENTRY_32(pos, LINUX_AT_CLKTCK, stclohz); AUXARGS_ENTRY_32(pos, AT_PHDR, args->phdr); AUXARGS_ENTRY_32(pos, AT_PHENT, args->phent); AUXARGS_ENTRY_32(pos, AT_PHNUM, args->phnum); AUXARGS_ENTRY_32(pos, AT_PAGESZ, args->pagesz); AUXARGS_ENTRY_32(pos, AT_FLAGS, args->flags); AUXARGS_ENTRY_32(pos, AT_ENTRY, args->entry); AUXARGS_ENTRY_32(pos, AT_BASE, args->base); AUXARGS_ENTRY_32(pos, LINUX_AT_SECURE, issetugid); AUXARGS_ENTRY_32(pos, AT_UID, imgp->proc->p_ucred->cr_ruid); AUXARGS_ENTRY_32(pos, AT_EUID, imgp->proc->p_ucred->cr_svuid); AUXARGS_ENTRY_32(pos, AT_GID, imgp->proc->p_ucred->cr_rgid); AUXARGS_ENTRY_32(pos, AT_EGID, imgp->proc->p_ucred->cr_svgid); AUXARGS_ENTRY_32(pos, LINUX_AT_PLATFORM, PTROUT(linux_platform)); AUXARGS_ENTRY(pos, LINUX_AT_RANDOM, PTROUT(imgp->canary)); if (imgp->execpathp != 0) AUXARGS_ENTRY(pos, LINUX_AT_EXECFN, PTROUT(imgp->execpathp)); if (args->execfd != -1) AUXARGS_ENTRY_32(pos, AT_EXECFD, args->execfd); AUXARGS_ENTRY_32(pos, AT_NULL, 0); free(imgp->auxargs, M_TEMP); imgp->auxargs = NULL; base--; suword32(base, (uint32_t)imgp->args->argc); *stack_base = (register_t *)base; return (0); } static void linux_rt_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask) { struct thread *td = curthread; struct proc *p = td->td_proc; struct sigacts *psp; struct trapframe *regs; struct l_rt_sigframe *fp, frame; int oonstack; int sig; int code; sig = ksi->ksi_signo; code = ksi->ksi_code; PROC_LOCK_ASSERT(p, MA_OWNED); psp = p->p_sigacts; mtx_assert(&psp->ps_mtx, MA_OWNED); regs = td->td_frame; oonstack = sigonstack(regs->tf_rsp); #ifdef DEBUG if (ldebug(rt_sendsig)) printf(ARGS(rt_sendsig, "%p, %d, %p, %u"), catcher, sig, (void*)mask, code); #endif /* * Allocate space for the signal handler context. */ if ((td->td_pflags & TDP_ALTSTACK) && !oonstack && SIGISMEMBER(psp->ps_sigonstack, sig)) { fp = (struct l_rt_sigframe *)((uintptr_t)td->td_sigstk.ss_sp + td->td_sigstk.ss_size - sizeof(struct l_rt_sigframe)); } else fp = (struct l_rt_sigframe *)regs->tf_rsp - 1; mtx_unlock(&psp->ps_mtx); /* * Build the argument list for the signal handler. */ sig = bsd_to_linux_signal(sig); bzero(&frame, sizeof(frame)); frame.sf_handler = PTROUT(catcher); frame.sf_sig = sig; frame.sf_siginfo = PTROUT(&fp->sf_si); frame.sf_ucontext = PTROUT(&fp->sf_sc); /* Fill in POSIX parts */ ksiginfo_to_lsiginfo(ksi, &frame.sf_si, sig); /* * Build the signal context to be used by sigreturn * and libgcc unwind. */ frame.sf_sc.uc_flags = 0; /* XXX ??? */ frame.sf_sc.uc_link = 0; /* XXX ??? */ frame.sf_sc.uc_stack.ss_sp = PTROUT(td->td_sigstk.ss_sp); frame.sf_sc.uc_stack.ss_size = td->td_sigstk.ss_size; frame.sf_sc.uc_stack.ss_flags = (td->td_pflags & TDP_ALTSTACK) ? ((oonstack) ? LINUX_SS_ONSTACK : 0) : LINUX_SS_DISABLE; PROC_UNLOCK(p); bsd_to_linux_sigset(mask, &frame.sf_sc.uc_sigmask); frame.sf_sc.uc_mcontext.sc_mask = frame.sf_sc.uc_sigmask.__mask; frame.sf_sc.uc_mcontext.sc_edi = regs->tf_rdi; frame.sf_sc.uc_mcontext.sc_esi = regs->tf_rsi; frame.sf_sc.uc_mcontext.sc_ebp = regs->tf_rbp; frame.sf_sc.uc_mcontext.sc_ebx = regs->tf_rbx; frame.sf_sc.uc_mcontext.sc_esp = regs->tf_rsp; frame.sf_sc.uc_mcontext.sc_edx = regs->tf_rdx; frame.sf_sc.uc_mcontext.sc_ecx = regs->tf_rcx; frame.sf_sc.uc_mcontext.sc_eax = regs->tf_rax; frame.sf_sc.uc_mcontext.sc_eip = regs->tf_rip; frame.sf_sc.uc_mcontext.sc_cs = regs->tf_cs; frame.sf_sc.uc_mcontext.sc_gs = regs->tf_gs; frame.sf_sc.uc_mcontext.sc_fs = regs->tf_fs; frame.sf_sc.uc_mcontext.sc_es = regs->tf_es; frame.sf_sc.uc_mcontext.sc_ds = regs->tf_ds; frame.sf_sc.uc_mcontext.sc_eflags = regs->tf_rflags; frame.sf_sc.uc_mcontext.sc_esp_at_signal = regs->tf_rsp; frame.sf_sc.uc_mcontext.sc_ss = regs->tf_ss; frame.sf_sc.uc_mcontext.sc_err = regs->tf_err; frame.sf_sc.uc_mcontext.sc_cr2 = (u_int32_t)(uintptr_t)ksi->ksi_addr; frame.sf_sc.uc_mcontext.sc_trapno = bsd_to_linux_trapcode(code); #ifdef DEBUG if (ldebug(rt_sendsig)) printf(LMSG("rt_sendsig flags: 0x%x, sp: %p, ss: 0x%lx, mask: 0x%x"), frame.sf_sc.uc_stack.ss_flags, td->td_sigstk.ss_sp, td->td_sigstk.ss_size, frame.sf_sc.uc_mcontext.sc_mask); #endif if (copyout(&frame, fp, sizeof(frame)) != 0) { /* * Process has trashed its stack; give it an illegal * instruction to halt it in its tracks. */ #ifdef DEBUG if (ldebug(rt_sendsig)) printf(LMSG("rt_sendsig: bad stack %p, oonstack=%x"), fp, oonstack); #endif PROC_LOCK(p); sigexit(td, SIGILL); } /* * Build context to run handler in. */ regs->tf_rsp = PTROUT(fp); regs->tf_rip = linux32_rt_sigcode; regs->tf_rflags &= ~(PSL_T | PSL_D); regs->tf_cs = _ucode32sel; regs->tf_ss = _udatasel; regs->tf_ds = _udatasel; regs->tf_es = _udatasel; regs->tf_fs = _ufssel; regs->tf_gs = _ugssel; regs->tf_flags = TF_HASSEGS; set_pcb_flags(td->td_pcb, PCB_FULL_IRET); PROC_LOCK(p); mtx_lock(&psp->ps_mtx); } /* * Send an interrupt to process. * * Stack is set up to allow sigcode stored * in u. to call routine, followed by kcall * to sigreturn routine below. After sigreturn * resets the signal mask, the stack, and the * frame pointer, it returns to the user * specified pc, psl. */ static void linux_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask) { struct thread *td = curthread; struct proc *p = td->td_proc; struct sigacts *psp; struct trapframe *regs; struct l_sigframe *fp, frame; l_sigset_t lmask; int oonstack; int sig, code; sig = ksi->ksi_signo; code = ksi->ksi_code; PROC_LOCK_ASSERT(p, MA_OWNED); psp = p->p_sigacts; mtx_assert(&psp->ps_mtx, MA_OWNED); if (SIGISMEMBER(psp->ps_siginfo, sig)) { /* Signal handler installed with SA_SIGINFO. */ linux_rt_sendsig(catcher, ksi, mask); return; } regs = td->td_frame; oonstack = sigonstack(regs->tf_rsp); #ifdef DEBUG if (ldebug(sendsig)) printf(ARGS(sendsig, "%p, %d, %p, %u"), catcher, sig, (void*)mask, code); #endif /* * Allocate space for the signal handler context. */ if ((td->td_pflags & TDP_ALTSTACK) && !oonstack && SIGISMEMBER(psp->ps_sigonstack, sig)) { fp = (struct l_sigframe *)((uintptr_t)td->td_sigstk.ss_sp + td->td_sigstk.ss_size - sizeof(struct l_sigframe)); } else fp = (struct l_sigframe *)regs->tf_rsp - 1; mtx_unlock(&psp->ps_mtx); PROC_UNLOCK(p); /* * Build the argument list for the signal handler. */ sig = bsd_to_linux_signal(sig); bzero(&frame, sizeof(frame)); frame.sf_handler = PTROUT(catcher); frame.sf_sig = sig; bsd_to_linux_sigset(mask, &lmask); /* * Build the signal context to be used by sigreturn. */ frame.sf_sc.sc_mask = lmask.__mask; frame.sf_sc.sc_gs = regs->tf_gs; frame.sf_sc.sc_fs = regs->tf_fs; frame.sf_sc.sc_es = regs->tf_es; frame.sf_sc.sc_ds = regs->tf_ds; frame.sf_sc.sc_edi = regs->tf_rdi; frame.sf_sc.sc_esi = regs->tf_rsi; frame.sf_sc.sc_ebp = regs->tf_rbp; frame.sf_sc.sc_ebx = regs->tf_rbx; frame.sf_sc.sc_esp = regs->tf_rsp; frame.sf_sc.sc_edx = regs->tf_rdx; frame.sf_sc.sc_ecx = regs->tf_rcx; frame.sf_sc.sc_eax = regs->tf_rax; frame.sf_sc.sc_eip = regs->tf_rip; frame.sf_sc.sc_cs = regs->tf_cs; frame.sf_sc.sc_eflags = regs->tf_rflags; frame.sf_sc.sc_esp_at_signal = regs->tf_rsp; frame.sf_sc.sc_ss = regs->tf_ss; frame.sf_sc.sc_err = regs->tf_err; frame.sf_sc.sc_cr2 = (u_int32_t)(uintptr_t)ksi->ksi_addr; frame.sf_sc.sc_trapno = bsd_to_linux_trapcode(code); frame.sf_extramask[0] = lmask.__mask; if (copyout(&frame, fp, sizeof(frame)) != 0) { /* * Process has trashed its stack; give it an illegal * instruction to halt it in its tracks. */ PROC_LOCK(p); sigexit(td, SIGILL); } /* * Build context to run handler in. */ regs->tf_rsp = PTROUT(fp); regs->tf_rip = linux32_sigcode; regs->tf_rflags &= ~(PSL_T | PSL_D); regs->tf_cs = _ucode32sel; regs->tf_ss = _udatasel; regs->tf_ds = _udatasel; regs->tf_es = _udatasel; regs->tf_fs = _ufssel; regs->tf_gs = _ugssel; regs->tf_flags = TF_HASSEGS; set_pcb_flags(td->td_pcb, PCB_FULL_IRET); PROC_LOCK(p); mtx_lock(&psp->ps_mtx); } /* * System call to cleanup state after a signal * has been taken. Reset signal mask and * stack state from context left by sendsig (above). * Return to previous pc and psl as specified by * context left by sendsig. Check carefully to * make sure that the user has not modified the * psl to gain improper privileges or to cause * a machine fault. */ int linux_sigreturn(struct thread *td, struct linux_sigreturn_args *args) { struct l_sigframe frame; struct trapframe *regs; sigset_t bmask; l_sigset_t lmask; int eflags; ksiginfo_t ksi; regs = td->td_frame; #ifdef DEBUG if (ldebug(sigreturn)) printf(ARGS(sigreturn, "%p"), (void *)args->sfp); #endif /* * The trampoline code hands us the sigframe. * It is unsafe to keep track of it ourselves, in the event that a * program jumps out of a signal handler. */ if (copyin(args->sfp, &frame, sizeof(frame)) != 0) return (EFAULT); /* * Check for security violations. */ #define EFLAGS_SECURE(ef, oef) ((((ef) ^ (oef)) & ~PSL_USERCHANGE) == 0) eflags = frame.sf_sc.sc_eflags; if (!EFLAGS_SECURE(eflags, regs->tf_rflags)) return(EINVAL); /* * Don't allow users to load a valid privileged %cs. Let the * hardware check for invalid selectors, excess privilege in * other selectors, invalid %eip's and invalid %esp's. */ #define CS_SECURE(cs) (ISPL(cs) == SEL_UPL) if (!CS_SECURE(frame.sf_sc.sc_cs)) { ksiginfo_init_trap(&ksi); ksi.ksi_signo = SIGBUS; ksi.ksi_code = BUS_OBJERR; ksi.ksi_trapno = T_PROTFLT; ksi.ksi_addr = (void *)regs->tf_rip; trapsignal(td, &ksi); return(EINVAL); } lmask.__mask = frame.sf_sc.sc_mask; lmask.__mask = frame.sf_extramask[0]; linux_to_bsd_sigset(&lmask, &bmask); kern_sigprocmask(td, SIG_SETMASK, &bmask, NULL, 0); /* * Restore signal context. */ regs->tf_rdi = frame.sf_sc.sc_edi; regs->tf_rsi = frame.sf_sc.sc_esi; regs->tf_rbp = frame.sf_sc.sc_ebp; regs->tf_rbx = frame.sf_sc.sc_ebx; regs->tf_rdx = frame.sf_sc.sc_edx; regs->tf_rcx = frame.sf_sc.sc_ecx; regs->tf_rax = frame.sf_sc.sc_eax; regs->tf_rip = frame.sf_sc.sc_eip; regs->tf_cs = frame.sf_sc.sc_cs; regs->tf_ds = frame.sf_sc.sc_ds; regs->tf_es = frame.sf_sc.sc_es; regs->tf_fs = frame.sf_sc.sc_fs; regs->tf_gs = frame.sf_sc.sc_gs; regs->tf_rflags = eflags; regs->tf_rsp = frame.sf_sc.sc_esp_at_signal; regs->tf_ss = frame.sf_sc.sc_ss; set_pcb_flags(td->td_pcb, PCB_FULL_IRET); return (EJUSTRETURN); } /* * System call to cleanup state after a signal * has been taken. Reset signal mask and * stack state from context left by rt_sendsig (above). * Return to previous pc and psl as specified by * context left by sendsig. Check carefully to * make sure that the user has not modified the * psl to gain improper privileges or to cause * a machine fault. */ int linux_rt_sigreturn(struct thread *td, struct linux_rt_sigreturn_args *args) { struct l_ucontext uc; struct l_sigcontext *context; sigset_t bmask; l_stack_t *lss; stack_t ss; struct trapframe *regs; int eflags; ksiginfo_t ksi; regs = td->td_frame; #ifdef DEBUG if (ldebug(rt_sigreturn)) printf(ARGS(rt_sigreturn, "%p"), (void *)args->ucp); #endif /* * The trampoline code hands us the ucontext. * It is unsafe to keep track of it ourselves, in the event that a * program jumps out of a signal handler. */ if (copyin(args->ucp, &uc, sizeof(uc)) != 0) return (EFAULT); context = &uc.uc_mcontext; /* * Check for security violations. */ #define EFLAGS_SECURE(ef, oef) ((((ef) ^ (oef)) & ~PSL_USERCHANGE) == 0) eflags = context->sc_eflags; if (!EFLAGS_SECURE(eflags, regs->tf_rflags)) return(EINVAL); /* * Don't allow users to load a valid privileged %cs. Let the * hardware check for invalid selectors, excess privilege in * other selectors, invalid %eip's and invalid %esp's. */ #define CS_SECURE(cs) (ISPL(cs) == SEL_UPL) if (!CS_SECURE(context->sc_cs)) { ksiginfo_init_trap(&ksi); ksi.ksi_signo = SIGBUS; ksi.ksi_code = BUS_OBJERR; ksi.ksi_trapno = T_PROTFLT; ksi.ksi_addr = (void *)regs->tf_rip; trapsignal(td, &ksi); return(EINVAL); } linux_to_bsd_sigset(&uc.uc_sigmask, &bmask); kern_sigprocmask(td, SIG_SETMASK, &bmask, NULL, 0); /* * Restore signal context */ regs->tf_gs = context->sc_gs; regs->tf_fs = context->sc_fs; regs->tf_es = context->sc_es; regs->tf_ds = context->sc_ds; regs->tf_rdi = context->sc_edi; regs->tf_rsi = context->sc_esi; regs->tf_rbp = context->sc_ebp; regs->tf_rbx = context->sc_ebx; regs->tf_rdx = context->sc_edx; regs->tf_rcx = context->sc_ecx; regs->tf_rax = context->sc_eax; regs->tf_rip = context->sc_eip; regs->tf_cs = context->sc_cs; regs->tf_rflags = eflags; regs->tf_rsp = context->sc_esp_at_signal; regs->tf_ss = context->sc_ss; set_pcb_flags(td->td_pcb, PCB_FULL_IRET); /* * call sigaltstack & ignore results.. */ lss = &uc.uc_stack; ss.ss_sp = PTRIN(lss->ss_sp); ss.ss_size = lss->ss_size; ss.ss_flags = linux_to_bsd_sigaltstack(lss->ss_flags); #ifdef DEBUG if (ldebug(rt_sigreturn)) printf(LMSG("rt_sigret flags: 0x%x, sp: %p, ss: 0x%lx, mask: 0x%x"), ss.ss_flags, ss.ss_sp, ss.ss_size, context->sc_mask); #endif (void)kern_sigaltstack(td, &ss, NULL); return (EJUSTRETURN); } static int linux32_fetch_syscall_args(struct thread *td, struct syscall_args *sa) { struct proc *p; struct trapframe *frame; p = td->td_proc; frame = td->td_frame; sa->args[0] = frame->tf_rbx; sa->args[1] = frame->tf_rcx; sa->args[2] = frame->tf_rdx; sa->args[3] = frame->tf_rsi; sa->args[4] = frame->tf_rdi; sa->args[5] = frame->tf_rbp; /* Unconfirmed */ sa->code = frame->tf_rax; if (sa->code >= p->p_sysent->sv_size) /* nosys */ sa->callp = &p->p_sysent->sv_table[p->p_sysent->sv_size - 1]; else sa->callp = &p->p_sysent->sv_table[sa->code]; sa->narg = sa->callp->sy_narg; td->td_retval[0] = 0; td->td_retval[1] = frame->tf_rdx; return (0); } /* * If a linux binary is exec'ing something, try this image activator * first. We override standard shell script execution in order to * be able to modify the interpreter path. We only do this if a linux * binary is doing the exec, so we do not create an EXEC module for it. */ static int exec_linux_imgact_try(struct image_params *iparams); static int exec_linux_imgact_try(struct image_params *imgp) { const char *head = (const char *)imgp->image_header; char *rpath; int error = -1; /* * The interpreter for shell scripts run from a linux binary needs * to be located in /compat/linux if possible in order to recursively * maintain linux path emulation. */ if (((const short *)head)[0] == SHELLMAGIC) { /* * Run our normal shell image activator. If it succeeds attempt * to use the alternate path for the interpreter. If an * alternate * path is found, use our stringspace to store it. */ if ((error = exec_shell_imgact(imgp)) == 0) { linux_emul_convpath(FIRST_THREAD_IN_PROC(imgp->proc), imgp->interpreter_name, UIO_SYSSPACE, &rpath, 0, AT_FDCWD); if (rpath != NULL) imgp->args->fname_buf = imgp->interpreter_name = rpath; } } return (error); } /* * Clear registers on exec * XXX copied from ia32_signal.c. */ static void exec_linux_setregs(struct thread *td, struct image_params *imgp, u_long stack) { struct trapframe *regs = td->td_frame; struct pcb *pcb = td->td_pcb; mtx_lock(&dt_lock); if (td->td_proc->p_md.md_ldt != NULL) user_ldt_free(td); else mtx_unlock(&dt_lock); critical_enter(); wrmsr(MSR_FSBASE, 0); wrmsr(MSR_KGSBASE, 0); /* User value while we're in the kernel */ pcb->pcb_fsbase = 0; pcb->pcb_gsbase = 0; critical_exit(); pcb->pcb_initial_fpucw = __LINUX_NPXCW__; bzero((char *)regs, sizeof(struct trapframe)); regs->tf_rip = imgp->entry_addr; regs->tf_rsp = stack; regs->tf_rflags = PSL_USER | (regs->tf_rflags & PSL_T); regs->tf_gs = _ugssel; regs->tf_fs = _ufssel; regs->tf_es = _udatasel; regs->tf_ds = _udatasel; regs->tf_ss = _udatasel; regs->tf_flags = TF_HASSEGS; regs->tf_cs = _ucode32sel; regs->tf_rbx = imgp->ps_strings; fpstate_drop(td); /* Do full restore on return so that we can change to a different %cs */ set_pcb_flags(pcb, PCB_32BIT | PCB_FULL_IRET); td->td_retval[1] = 0; } /* * XXX copied from ia32_sysvec.c. */ static register_t * linux_copyout_strings(struct image_params *imgp) { int argc, envc; u_int32_t *vectp; char *stringp, *destp; u_int32_t *stack_base; struct linux32_ps_strings *arginfo; char canary[LINUX_AT_RANDOM_LEN]; size_t execpath_len; /* * Calculate string base and vector table pointers. */ if (imgp->execpath != NULL && imgp->auxargs != NULL) execpath_len = strlen(imgp->execpath) + 1; else execpath_len = 0; arginfo = (struct linux32_ps_strings *)LINUX32_PS_STRINGS; destp = (caddr_t)arginfo - SPARE_USRSPACE - roundup(sizeof(canary), sizeof(char *)) - roundup(execpath_len, sizeof(char *)) - roundup((ARG_MAX - imgp->args->stringspace), sizeof(char *)); if (execpath_len != 0) { imgp->execpathp = (uintptr_t)arginfo - execpath_len; copyout(imgp->execpath, (void *)imgp->execpathp, execpath_len); } /* * Prepare the canary for SSP. */ arc4rand(canary, sizeof(canary), 0); imgp->canary = (uintptr_t)arginfo - roundup(execpath_len, sizeof(char *)) - roundup(sizeof(canary), sizeof(char *)); copyout(canary, (void *)imgp->canary, sizeof(canary)); /* * If we have a valid auxargs ptr, prepare some room * on the stack. */ if (imgp->auxargs) { /* * 'AT_COUNT*2' is size for the ELF Auxargs data. This is for * lower compatibility. */ imgp->auxarg_size = (imgp->auxarg_size) ? imgp->auxarg_size : (LINUX_AT_COUNT * 2); /* * The '+ 2' is for the null pointers at the end of each of * the arg and env vector sets,and imgp->auxarg_size is room * for argument of Runtime loader. */ vectp = (u_int32_t *) (destp - (imgp->args->argc + imgp->args->envc + 2 + imgp->auxarg_size) * sizeof(u_int32_t)); } else /* * The '+ 2' is for the null pointers at the end of each of * the arg and env vector sets */ vectp = (u_int32_t *)(destp - (imgp->args->argc + imgp->args->envc + 2) * sizeof(u_int32_t)); /* * vectp also becomes our initial stack base */ stack_base = vectp; stringp = imgp->args->begin_argv; argc = imgp->args->argc; envc = imgp->args->envc; /* * Copy out strings - arguments and environment. */ copyout(stringp, destp, ARG_MAX - imgp->args->stringspace); /* * Fill in "ps_strings" struct for ps, w, etc. */ suword32(&arginfo->ps_argvstr, (uint32_t)(intptr_t)vectp); suword32(&arginfo->ps_nargvstr, argc); /* * Fill in argument portion of vector table. */ for (; argc > 0; --argc) { suword32(vectp++, (uint32_t)(intptr_t)destp); while (*stringp++ != 0) destp++; destp++; } /* a null vector table pointer separates the argp's from the envp's */ suword32(vectp++, 0); suword32(&arginfo->ps_envstr, (uint32_t)(intptr_t)vectp); suword32(&arginfo->ps_nenvstr, envc); /* * Fill in environment portion of vector table. */ for (; envc > 0; --envc) { suword32(vectp++, (uint32_t)(intptr_t)destp); while (*stringp++ != 0) destp++; destp++; } /* end of vector table is a null pointer */ suword32(vectp, 0); return ((register_t *)stack_base); } static SYSCTL_NODE(_compat, OID_AUTO, linux32, CTLFLAG_RW, 0, "32-bit Linux emulation"); static u_long linux32_maxdsiz = LINUX32_MAXDSIZ; SYSCTL_ULONG(_compat_linux32, OID_AUTO, maxdsiz, CTLFLAG_RW, &linux32_maxdsiz, 0, ""); static u_long linux32_maxssiz = LINUX32_MAXSSIZ; SYSCTL_ULONG(_compat_linux32, OID_AUTO, maxssiz, CTLFLAG_RW, &linux32_maxssiz, 0, ""); static u_long linux32_maxvmem = LINUX32_MAXVMEM; SYSCTL_ULONG(_compat_linux32, OID_AUTO, maxvmem, CTLFLAG_RW, &linux32_maxvmem, 0, ""); #if defined(DEBUG) SYSCTL_PROC(_compat_linux32, OID_AUTO, debug, CTLTYPE_STRING | CTLFLAG_RW, 0, 0, linux_sysctl_debug, "A", "Linux debugging control"); #endif static void linux32_fixlimit(struct rlimit *rl, int which) { switch (which) { case RLIMIT_DATA: if (linux32_maxdsiz != 0) { if (rl->rlim_cur > linux32_maxdsiz) rl->rlim_cur = linux32_maxdsiz; if (rl->rlim_max > linux32_maxdsiz) rl->rlim_max = linux32_maxdsiz; } break; case RLIMIT_STACK: if (linux32_maxssiz != 0) { if (rl->rlim_cur > linux32_maxssiz) rl->rlim_cur = linux32_maxssiz; if (rl->rlim_max > linux32_maxssiz) rl->rlim_max = linux32_maxssiz; } break; case RLIMIT_VMEM: if (linux32_maxvmem != 0) { if (rl->rlim_cur > linux32_maxvmem) rl->rlim_cur = linux32_maxvmem; if (rl->rlim_max > linux32_maxvmem) rl->rlim_max = linux32_maxvmem; } break; } } struct sysentvec elf_linux_sysvec = { .sv_size = LINUX32_SYS_MAXSYSCALL, .sv_table = linux32_sysent, .sv_mask = 0, .sv_errsize = ELAST + 1, .sv_errtbl = bsd_to_linux_errno, .sv_transtrap = translate_traps, .sv_fixup = elf_linux_fixup, .sv_sendsig = linux_sendsig, .sv_sigcode = &_binary_linux32_locore_o_start, .sv_szsigcode = &linux_szsigcode, .sv_name = "Linux ELF32", .sv_coredump = elf32_coredump, .sv_imgact_try = exec_linux_imgact_try, .sv_minsigstksz = LINUX_MINSIGSTKSZ, .sv_pagesize = PAGE_SIZE, .sv_minuser = VM_MIN_ADDRESS, .sv_maxuser = LINUX32_MAXUSER, .sv_usrstack = LINUX32_USRSTACK, .sv_psstrings = LINUX32_PS_STRINGS, .sv_stackprot = VM_PROT_ALL, .sv_copyout_strings = linux_copyout_strings, .sv_setregs = exec_linux_setregs, .sv_fixlimit = linux32_fixlimit, .sv_maxssiz = &linux32_maxssiz, .sv_flags = SV_ABI_LINUX | SV_ILP32 | SV_IA32 | SV_SHP, .sv_set_syscall_retval = cpu_set_syscall_retval, .sv_fetch_syscall_args = linux32_fetch_syscall_args, .sv_syscallnames = NULL, .sv_shared_page_base = LINUX32_SHAREDPAGE, .sv_shared_page_len = PAGE_SIZE, .sv_schedtail = linux_schedtail, .sv_thread_detach = linux_thread_detach, .sv_trap = NULL, }; static void linux_vdso_install(void *param) { linux_szsigcode = (&_binary_linux32_locore_o_end - &_binary_linux32_locore_o_start); if (linux_szsigcode > elf_linux_sysvec.sv_shared_page_len) panic("Linux invalid vdso size\n"); __elfN(linux_vdso_fixup)(&elf_linux_sysvec); linux_shared_page_obj = __elfN(linux_shared_page_init) (&linux_shared_page_mapping); __elfN(linux_vdso_reloc)(&elf_linux_sysvec, LINUX32_SHAREDPAGE); bcopy(elf_linux_sysvec.sv_sigcode, linux_shared_page_mapping, linux_szsigcode); elf_linux_sysvec.sv_shared_page_obj = linux_shared_page_obj; linux_kplatform = linux_shared_page_mapping + (linux_platform - (caddr_t)LINUX32_SHAREDPAGE); } SYSINIT(elf_linux_vdso_init, SI_SUB_EXEC, SI_ORDER_ANY, (sysinit_cfunc_t)linux_vdso_install, NULL); static void linux_vdso_deinstall(void *param) { __elfN(linux_shared_page_fini)(linux_shared_page_obj); }; SYSUNINIT(elf_linux_vdso_uninit, SI_SUB_EXEC, SI_ORDER_FIRST, (sysinit_cfunc_t)linux_vdso_deinstall, NULL); static char GNU_ABI_VENDOR[] = "GNU"; static int GNULINUX_ABI_DESC = 0; static boolean_t linux32_trans_osrel(const Elf_Note *note, int32_t *osrel) { const Elf32_Word *desc; uintptr_t p; p = (uintptr_t)(note + 1); p += roundup2(note->n_namesz, sizeof(Elf32_Addr)); desc = (const Elf32_Word *)p; if (desc[0] != GNULINUX_ABI_DESC) return (FALSE); /* * For linux we encode osrel as follows (see linux_mib.c): * VVVMMMIII (version, major, minor), see linux_mib.c. */ *osrel = desc[1] * 1000000 + desc[2] * 1000 + desc[3]; return (TRUE); } static Elf_Brandnote linux32_brandnote = { .hdr.n_namesz = sizeof(GNU_ABI_VENDOR), .hdr.n_descsz = 16, /* XXX at least 16 */ .hdr.n_type = 1, .vendor = GNU_ABI_VENDOR, .flags = BN_TRANSLATE_OSREL, .trans_osrel = linux32_trans_osrel }; static Elf32_Brandinfo linux_brand = { .brand = ELFOSABI_LINUX, .machine = EM_386, .compat_3_brand = "Linux", .emul_path = "/compat/linux", .interp_path = "/lib/ld-linux.so.1", .sysvec = &elf_linux_sysvec, .interp_newpath = NULL, .brand_note = &linux32_brandnote, .flags = BI_CAN_EXEC_DYN | BI_BRAND_NOTE }; static Elf32_Brandinfo linux_glibc2brand = { .brand = ELFOSABI_LINUX, .machine = EM_386, .compat_3_brand = "Linux", .emul_path = "/compat/linux", .interp_path = "/lib/ld-linux.so.2", .sysvec = &elf_linux_sysvec, .interp_newpath = NULL, .brand_note = &linux32_brandnote, .flags = BI_CAN_EXEC_DYN | BI_BRAND_NOTE }; Elf32_Brandinfo *linux_brandlist[] = { &linux_brand, &linux_glibc2brand, NULL }; static int linux_elf_modevent(module_t mod, int type, void *data) { Elf32_Brandinfo **brandinfo; int error; struct linux_ioctl_handler **lihp; error = 0; switch(type) { case MOD_LOAD: for (brandinfo = &linux_brandlist[0]; *brandinfo != NULL; ++brandinfo) if (elf32_insert_brand_entry(*brandinfo) < 0) error = EINVAL; if (error == 0) { SET_FOREACH(lihp, linux_ioctl_handler_set) linux_ioctl_register_handler(*lihp); LIST_INIT(&futex_list); mtx_init(&futex_mtx, "ftllk", NULL, MTX_DEF); stclohz = (stathz ? stathz : hz); if (bootverbose) printf("Linux ELF exec handler installed\n"); } else printf("cannot insert Linux ELF brand handler\n"); break; case MOD_UNLOAD: for (brandinfo = &linux_brandlist[0]; *brandinfo != NULL; ++brandinfo) if (elf32_brand_inuse(*brandinfo)) error = EBUSY; if (error == 0) { for (brandinfo = &linux_brandlist[0]; *brandinfo != NULL; ++brandinfo) if (elf32_remove_brand_entry(*brandinfo) < 0) error = EINVAL; } if (error == 0) { SET_FOREACH(lihp, linux_ioctl_handler_set) linux_ioctl_unregister_handler(*lihp); mtx_destroy(&futex_mtx); if (bootverbose) printf("Linux ELF exec handler removed\n"); } else printf("Could not deinstall ELF interpreter entry\n"); break; default: return (EOPNOTSUPP); } return (error); } static moduledata_t linux_elf_mod = { "linuxelf", linux_elf_modevent, 0 }; DECLARE_MODULE_TIED(linuxelf, linux_elf_mod, SI_SUB_EXEC, SI_ORDER_ANY); MODULE_DEPEND(linuxelf, linux_common, 1, 1, 1); +FEATURE(linux, "Linux 32bit support"); Index: projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2835_gpio.c =================================================================== --- projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2835_gpio.c (revision 297604) +++ projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2835_gpio.c (revision 297605) @@ -1,1034 +1,1407 @@ /*- * 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 ARM_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 }, - { SYS_RES_IRQ, 1, RF_ACTIVE }, - { SYS_RES_IRQ, 2, RF_ACTIVE }, - { SYS_RES_IRQ, 3, 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 ARM_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 ARM_INTRNG struct intr_event * sc_events[BCM_GPIO_PINS]; +#endif struct bcm_gpio_sysctl sc_sysctl[BCM_GPIO_PINS]; +#ifdef ARM_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 ARM_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 ARM_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 ARM_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 ARM_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 ARM_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 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); + 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; + + if (data == NULL || data->type != INTR_MAP_DATA_FDT) + return (ENOTSUP); + + sc = device_get_softc(dev); + bgi = (struct bcm_gpio_irqsrc *)isrc; + + /* Get and check config for an interrupt. */ + if (bcm_gpio_pic_map_fdt(sc, data->fdt.ncells, data->fdt.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 ARM_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: projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2835_intr.c =================================================================== --- projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2835_intr.c (revision 297604) +++ projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2835_intr.c (revision 297605) @@ -1,249 +1,537 @@ /*- * 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 ARM_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 ARM_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 ARM_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 ARM_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 ARM_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 ARM_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 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]; + 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 ARM_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 ARM_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 ARM_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 ARM_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: projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2836.c =================================================================== --- projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2836.c (revision 297604) +++ projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2836.c (revision 297605) @@ -1,216 +1,928 @@ /* * 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 ARM_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 ARM_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 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) + return (EINVAL); + + sc = device_get_softc(dev); + *isrcp = &sc->bls_isrcs[data->fdt.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 bool +bcm_lint_init_on_ap(struct bcm_lintc_softc *sc, struct bcm_lintc_irqsrc *bli, + u_int cpu) +{ + struct intr_irqsrc *isrc; + + isrc = &bli->bli_isrc; + + KASSERT(isrc->isrc_flags & INTR_ISRCF_PPI, + ("%s: irq %d is not PPI", __func__, bli->bli_irq)); + + if (isrc->isrc_handlers == 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); +} + +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 (bcm_lint_init_on_ap(sc, &sc->bls_isrcs[irq], cpu)) + bcm_lintc_rwreg_set(sc, reg, mask); +} + +static void +bcm_lintc_init_pmu_on_ap(struct bcm_lintc_softc *sc, u_int cpu) +{ + + if (bcm_lint_init_on_ap(sc, &sc->bls_isrcs[BCM_LINTC_PMU_IRQ], 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: projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2836.h =================================================================== --- projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2836.h (revision 297604) +++ projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2836.h (revision 297605) @@ -1,39 +1,40 @@ /* * Copyright 2015 Andrew Turner. * 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 _BCM2815_BCM2836_H #define _BCM2815_BCM2836_H +#ifndef ARM_INTRNG #define BCM2836_GPU_IRQ 8 int bcm2836_get_next_irq(int); void bcm2836_mask_irq(uintptr_t); void bcm2836_unmask_irq(uintptr_t); - +#endif #endif Index: projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2836_mp.c =================================================================== --- projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2836_mp.c (revision 297604) +++ projects/release-pkg/sys/arm/broadcom/bcm2835/bcm2836_mp.c (revision 297605) @@ -1,178 +1,180 @@ /*- * Copyright (C) 2015 Daisuke Aoyama * 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 * * 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 #ifdef DEBUG #define DPRINTF(fmt, ...) do { \ printf("%s:%u: ", __func__, __LINE__); \ printf(fmt, ##__VA_ARGS__); \ } while (0) #else #define DPRINTF(fmt, ...) #endif #define ARM_LOCAL_BASE 0x40000000 #define ARM_LOCAL_SIZE 0x00001000 /* mailbox registers */ #define MBOXINTRCTRL_CORE(n) (0x00000050 + (0x04 * (n))) #define MBOX0SET_CORE(n) (0x00000080 + (0x10 * (n))) #define MBOX1SET_CORE(n) (0x00000084 + (0x10 * (n))) #define MBOX2SET_CORE(n) (0x00000088 + (0x10 * (n))) #define MBOX3SET_CORE(n) (0x0000008C + (0x10 * (n))) #define MBOX0CLR_CORE(n) (0x000000C0 + (0x10 * (n))) #define MBOX1CLR_CORE(n) (0x000000C4 + (0x10 * (n))) #define MBOX2CLR_CORE(n) (0x000000C8 + (0x10 * (n))) #define MBOX3CLR_CORE(n) (0x000000CC + (0x10 * (n))) static bus_space_handle_t bs_periph; #define BSRD4(addr) \ bus_space_read_4(fdtbus_bs_tag, bs_periph, (addr)) #define BSWR4(addr, val) \ bus_space_write_4(fdtbus_bs_tag, bs_periph, (addr), (val)) void platform_mp_setmaxid(void) { DPRINTF("platform_mp_setmaxid\n"); if (mp_ncpus != 0) return; mp_ncpus = 4; mp_maxid = mp_ncpus - 1; DPRINTF("mp_maxid=%d\n", mp_maxid); } void platform_mp_start_ap(void) { uint32_t val; int i, retry; DPRINTF("platform_mp_start_ap\n"); /* initialize */ if (bus_space_map(fdtbus_bs_tag, ARM_LOCAL_BASE, ARM_LOCAL_SIZE, 0, &bs_periph) != 0) panic("can't map local peripheral\n"); for (i = 0; i < mp_ncpus; i++) { /* clear mailbox 0/3 */ BSWR4(MBOX0CLR_CORE(i), 0xffffffff); BSWR4(MBOX3CLR_CORE(i), 0xffffffff); } wmb(); dcache_wbinv_poc_all(); /* boot secondary CPUs */ for (i = 1; i < mp_ncpus; i++) { /* set entry point to mailbox 3 */ BSWR4(MBOX3SET_CORE(i), (uint32_t)pmap_kextract((vm_offset_t)mpentry)); wmb(); /* wait for bootup */ retry = 1000; do { /* check entry point */ val = BSRD4(MBOX3CLR_CORE(i)); if (val == 0) break; DELAY(100); retry--; if (retry <= 0) { printf("can't start for CPU%d\n", i); break; } } while (1); /* dsb and sev */ armv7_sev(); /* recode AP in CPU map */ CPU_SET(i, &all_cpus); } } +#ifndef ARM_INTRNG void pic_ipi_send(cpuset_t cpus, u_int ipi) { int i; dsb(); for (i = 0; i < mp_ncpus; i++) { if (CPU_ISSET(i, &cpus)) BSWR4(MBOX0SET_CORE(i), 1 << ipi); } wmb(); } int pic_ipi_read(int i) { uint32_t val; int cpu, ipi; cpu = PCPU_GET(cpuid); dsb(); if (i != -1) { val = BSRD4(MBOX0CLR_CORE(cpu)); if (val == 0) return (0); ipi = ffs(val) - 1; BSWR4(MBOX0CLR_CORE(cpu), 1 << ipi); dsb(); return (ipi); } return (0x3ff); } void pic_ipi_clear(int ipi) { } +#endif Index: projects/release-pkg/sys/arm/conf/RPI-B =================================================================== --- projects/release-pkg/sys/arm/conf/RPI-B (revision 297604) +++ projects/release-pkg/sys/arm/conf/RPI-B (revision 297605) @@ -1,111 +1,113 @@ # # RPI-B -- Custom configuration for the Raspberry Pi # # 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 RPI-B include "std.armv6" include "../broadcom/bcm2835/std.rpi" include "../broadcom/bcm2835/std.bcm2835" +options ARM_INTRNG + options HZ=100 options SCHED_4BSD # 4BSD scheduler options PLATFORM # 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=ue0 #options ROOTDEVNAME=\"ufs:mmcsd0s2\" device bpf device loop device ether device uart device pty device snp device pl011 # Comment following lines for boot console on serial port device vt device kbdmux device ukbd device sdhci device mmc device mmcsd device gpio device gpioled # I2C device iic device iicbus device bcm2835_bsc device md device random # Entropy device # USB support device usb options USB_DEBUG device dwcotg # DWC OTG controller # USB storage support device scbus device da device umass # USB ethernet support device smcphy device mii device smsc # SPI device spibus device bcm2835_spi device vchiq device sound # Flattened Device Tree options FDT # Configure using FDT/DTB data # Note: DTB is normally loaded and modified by RPi boot loader, then # handed to kernel via U-Boot and ubldr. #options FDT_DTB_STATIC #makeoptions FDT_DTS_FILE=rpi.dts makeoptions MODULES_EXTRA=dtb/rpi Index: projects/release-pkg/sys/arm/conf/RPI2 =================================================================== --- projects/release-pkg/sys/arm/conf/RPI2 (revision 297604) +++ projects/release-pkg/sys/arm/conf/RPI2 (revision 297605) @@ -1,115 +1,117 @@ # # RPI2 -- Custom configuration for the Raspberry Pi 2 # # 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 RPI2 include "std.armv6" include "../broadcom/bcm2835/std.rpi" include "../broadcom/bcm2835/std.bcm2836" +options ARM_INTRNG + options HZ=100 options SCHED_ULE # ULE scheduler options SMP # Enable multiple cores options PLATFORM # 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=ue0 options ROOTDEVNAME=\"ufs:mmcsd0s2\" # ARM Generic Timer device generic_timer device bpf device loop device ether device uart device pty device snp device pl011 # Comment following lines for boot console on serial port device vt device kbdmux device ukbd device sdhci device mmc device mmcsd device gpio device gpioled # I2C device iic device iicbus device bcm2835_bsc device md device random # Entropy device # USB support device usb options USB_DEBUG device dwcotg # DWC OTG controller # USB storage support device scbus device da device umass # USB ethernet support device smcphy device mii device smsc # SPI device spibus device bcm2835_spi device vchiq device sound # Flattened Device Tree options FDT # Configure using FDT/DTB data # Note: DTB is normally loaded and modified by RPi boot loader, then # handed to kernel via U-Boot and ubldr. #options FDT_DTB_STATIC #makeoptions FDT_DTS_FILE=rpi2.dts makeoptions MODULES_EXTRA=dtb/rpi Index: projects/release-pkg/sys/arm/nvidia/tegra124/tegra124_clk_pll.c =================================================================== --- projects/release-pkg/sys/arm/nvidia/tegra124/tegra124_clk_pll.c (revision 297604) +++ projects/release-pkg/sys/arm/nvidia/tegra124/tegra124_clk_pll.c (revision 297605) @@ -1,1066 +1,1060 @@ /*- * 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$"); #include #include #include #include #include #include #include #include #include #include "tegra124_car.h" /* #define TEGRA_PLL_DEBUG */ #ifdef TEGRA_PLL_DEBUG #define dprintf(...) printf(__VA_ARGS__) #else #define dprintf(...) #endif /* All PLLs. */ enum pll_type { PLL_M, PLL_X, PLL_C, PLL_C2, PLL_C3, PLL_C4, PLL_P, PLL_A, PLL_U, PLL_D, PLL_D2, PLL_DP, PLL_E, PLL_REFE}; /* Common base register bits. */ #define PLL_BASE_BYPASS (1U << 31) #define PLL_BASE_ENABLE (1 << 30) #define PLL_BASE_REFDISABLE (1 << 29) #define PLL_BASE_LOCK (1 << 27) #define PLL_BASE_DIVM_SHIFT 0 #define PLL_BASE_DIVN_SHIFT 8 #define PLLRE_MISC_LOCK (1 << 24) #define PLL_MISC_LOCK_ENABLE (1 << 18) #define PLLC_MISC_LOCK_ENABLE (1 << 24) #define PLLDU_MISC_LOCK_ENABLE (1 << 22) #define PLLRE_MISC_LOCK_ENABLE (1 << 30) #define PLLSS_MISC_LOCK_ENABLE (1 << 30) #define PLLC_IDDQ_BIT 26 #define PLLX_IDDQ_BIT 3 #define PLLRE_IDDQ_BIT 16 #define PLLSS_IDDQ_BIT 19 #define PLL_LOCK_TIMEOUT 1000 /* Post divider <-> register value mapping. */ struct pdiv_table { uint32_t divider; /* real divider */ uint32_t value; /* register value */ }; /* Bits definition of M, N and P fields. */ struct mnp_bits { uint32_t m_width; uint32_t n_width; uint32_t p_width; uint32_t p_shift; }; struct clk_pll_def { struct clknode_init_def clkdef; enum pll_type type; uint32_t base_reg; uint32_t misc_reg; uint32_t lock_mask; uint32_t lock_enable; uint32_t iddq_reg; uint32_t iddq_mask; uint32_t flags; struct pdiv_table *pdiv_table; struct mnp_bits mnp_bits; }; #define PLL(_id, cname, pname) \ .clkdef.id = _id, \ .clkdef.name = cname, \ .clkdef.parent_names = (const char *[]){pname}, \ .clkdef.parent_cnt = 1, \ .clkdef.flags = CLK_NODE_STATIC_STRINGS /* Tegra K1 PLLs PLLM: Clock source for EMC 2x clock PLLX: Clock source for the fast CPU cluster and the shadow CPU PLLC: Clock source for general use PLLC2: Clock source for engine scaling PLLC3: Clock source for engine scaling PLLC4: Clock source for ISP/VI units PLLP: Clock source for most peripherals PLLA: Audio clock sources: (11.2896 MHz, 12.288 MHz, 24.576 MHz) PLLU: Clock source for USB PHY, provides 12/60/480 MHz PLLD: Clock sources for the DSI and display subsystem PLLD2: Clock sources for the DSI and display subsystem refPLLe: PLLE: generate the 100 MHz reference clock for USB 3.0 (spread spectrum) PLLDP: Clock source for eDP/LVDS (spread spectrum) DFLLCPU: DFLL clock source for the fast CPU cluster GPCPLL: Clock source for the GPU */ static struct pdiv_table pllm_map[] = { {1, 0}, {2, 1}, {0, 0} }; static struct pdiv_table pllxc_map[] = { { 1, 0}, { 2, 1}, { 3, 2}, { 4, 3}, { 5, 4}, { 6, 5}, { 8, 6}, {10, 7}, {12, 8}, {16, 9}, {12, 10}, {16, 11}, {20, 12}, {24, 13}, {32, 14}, { 0, 0} }; static struct pdiv_table pllc_map[] = { { 1, 0}, { 2, 1}, { 3, 2}, { 4, 3}, { 6, 4}, { 8, 5}, {12, 6}, {16, 7}, { 0, 0} }; static struct pdiv_table pll12g_ssd_esd_map[] = { { 1, 0}, { 2, 1}, { 3, 2}, { 4, 3}, { 5, 4}, { 6, 5}, { 8, 6}, {10, 7}, {12, 8}, {16, 9}, {12, 10}, {16, 11}, {20, 12}, {24, 13}, {32, 14}, { 0, 0} }; static struct pdiv_table pllu_map[] = { {1, 1}, {2, 0}, {0, 0} }; static struct clk_pll_def pll_clks[] = { /* PLLM: 880 MHz Clock source for EMC 2x clock */ { PLL(TEGRA124_CLK_PLL_M, "pllM_out0", "osc_div_clk"), .type = PLL_M, .base_reg = PLLM_BASE, .misc_reg = PLLM_MISC, .lock_mask = PLL_BASE_LOCK, .lock_enable = PLL_MISC_LOCK_ENABLE, .pdiv_table = pllm_map, .mnp_bits = {8, 8, 1, 20}, }, /* PLLX: 1GHz Clock source for the fast CPU cluster and the shadow CPU */ { PLL(TEGRA124_CLK_PLL_X, "pllX_out", "osc_div_clk"), .type = PLL_X, .base_reg = PLLX_BASE, .misc_reg = PLLX_MISC, .lock_mask = PLL_BASE_LOCK, .lock_enable = PLL_MISC_LOCK_ENABLE, .iddq_reg = PLLX_MISC3, .iddq_mask = 1 << PLLX_IDDQ_BIT, .pdiv_table = pllxc_map, .mnp_bits = {8, 8, 4, 20}, }, /* PLLC: 600 MHz Clock source for general use */ { PLL(TEGRA124_CLK_PLL_C, "pllC_out0", "osc_div_clk"), .type = PLL_C, .base_reg = PLLC_BASE, .misc_reg = PLLC_MISC, .lock_mask = PLL_BASE_LOCK, .lock_enable = PLLC_MISC_LOCK_ENABLE, .iddq_reg = PLLC_MISC, .iddq_mask = 1 << PLLC_IDDQ_BIT, .pdiv_table = pllc_map, .mnp_bits = {8, 8, 4, 20}, }, /* PLLC2: 600 MHz Clock source for engine scaling */ { PLL(TEGRA124_CLK_PLL_C2, "pllC2_out0", "osc_div_clk"), .type = PLL_C2, .base_reg = PLLC2_BASE, .misc_reg = PLLC2_MISC, .lock_mask = PLL_BASE_LOCK, .lock_enable = PLL_MISC_LOCK_ENABLE, .pdiv_table = pllc_map, .mnp_bits = {2, 8, 3, 20}, }, /* PLLC3: 600 MHz Clock source for engine scaling */ { PLL(TEGRA124_CLK_PLL_C3, "pllC3_out0", "osc_div_clk"), .type = PLL_C3, .base_reg = PLLC3_BASE, .misc_reg = PLLC3_MISC, .lock_mask = PLL_BASE_LOCK, .lock_enable = PLL_MISC_LOCK_ENABLE, .pdiv_table = pllc_map, .mnp_bits = {2, 8, 3, 20}, }, /* PLLC4: 600 MHz Clock source for ISP/VI units */ { PLL(TEGRA124_CLK_PLL_C4, "pllC4_out0", "pllC4_src"), .type = PLL_C4, .base_reg = PLLC4_BASE, .misc_reg = PLLC4_MISC, .lock_mask = PLL_BASE_LOCK, .lock_enable = PLLSS_MISC_LOCK_ENABLE, .iddq_reg = PLLC4_BASE, .iddq_mask = 1 << PLLSS_IDDQ_BIT, .pdiv_table = pll12g_ssd_esd_map, .mnp_bits = {8, 8, 4, 20}, }, /* PLLP: 408 MHz Clock source for most peripherals */ { PLL(TEGRA124_CLK_PLL_P, "pllP_out0", "osc_div_clk"), .type = PLL_P, .base_reg = PLLP_BASE, .misc_reg = PLLP_MISC, .lock_mask = PLL_BASE_LOCK, .lock_enable = PLL_MISC_LOCK_ENABLE, .mnp_bits = {5, 10, 3, 20}, }, /* PLLA: Audio clock sources: (11.2896 MHz, 12.288 MHz, 24.576 MHz) */ { PLL(TEGRA124_CLK_PLL_A, "pllA_out", "pllP_out1"), .type = PLL_A, .base_reg = PLLA_BASE, .misc_reg = PLLA_MISC, .lock_mask = PLL_BASE_LOCK, .lock_enable = PLL_MISC_LOCK_ENABLE, .mnp_bits = {5, 10, 3, 20}, }, /* PLLU: 480 MHz Clock source for USB PHY, provides 12/60/480 MHz */ { PLL(TEGRA124_CLK_PLL_U, "pllU_out", "osc_div_clk"), .type = PLL_U, .base_reg = PLLU_BASE, .misc_reg = PLLU_MISC, .lock_mask = PLL_BASE_LOCK, .lock_enable = PLLDU_MISC_LOCK_ENABLE, .pdiv_table = pllu_map, .mnp_bits = {5, 10, 1, 20}, }, /* PLLD: 600 MHz Clock sources for the DSI and display subsystem */ { PLL(TEGRA124_CLK_PLL_D, "pllD_out", "osc_div_clk"), .type = PLL_D, .base_reg = PLLD_BASE, .misc_reg = PLLD_MISC, .lock_mask = PLL_BASE_LOCK, .lock_enable = PLL_MISC_LOCK_ENABLE, .mnp_bits = {5, 11, 3, 20}, }, /* PLLD2: 600 MHz Clock sources for the DSI and display subsystem */ { PLL(TEGRA124_CLK_PLL_D2, "pllD2_out", "pllD2_src"), .type = PLL_D2, .base_reg = PLLD2_BASE, .misc_reg = PLLD2_MISC, .lock_mask = PLL_BASE_LOCK, .lock_enable = PLLSS_MISC_LOCK_ENABLE, .iddq_reg = PLLD2_BASE, .iddq_mask = 1 << PLLSS_IDDQ_BIT, .pdiv_table = pll12g_ssd_esd_map, .mnp_bits = {8, 8, 4, 20}, }, /* refPLLe: */ { PLL(0, "pllREFE_out", "osc_div_clk"), .type = PLL_REFE, .base_reg = PLLRE_BASE, .misc_reg = PLLRE_MISC, .lock_mask = PLLRE_MISC_LOCK, .lock_enable = PLLRE_MISC_LOCK_ENABLE, .iddq_reg = PLLRE_MISC, .iddq_mask = 1 << PLLRE_IDDQ_BIT, .mnp_bits = {8, 8, 4, 16}, }, /* PLLE: generate the 100 MHz reference clock for USB 3.0 (spread spectrum) */ { PLL(TEGRA124_CLK_PLL_E, "pllE_out0", "pllE_src"), .type = PLL_E, .base_reg = PLLE_BASE, .misc_reg = PLLE_MISC, .lock_mask = PLLE_MISC_LOCK, .lock_enable = PLLE_MISC_LOCK_ENABLE, .mnp_bits = {8, 8, 4, 24}, }, /* PLLDP: 600 MHz Clock source for eDP/LVDS (spread spectrum) */ { PLL(0, "pllDP_out0", "pllDP_src"), .type = PLL_DP, .base_reg = PLLDP_BASE, .misc_reg = PLLDP_MISC, .lock_mask = PLL_BASE_LOCK, .lock_enable = PLLSS_MISC_LOCK_ENABLE, .iddq_reg = PLLDP_BASE, .iddq_mask = 1 << PLLSS_IDDQ_BIT, .pdiv_table = pll12g_ssd_esd_map, .mnp_bits = {8, 8, 4, 20}, }, }; static int tegra124_pll_init(struct clknode *clk, device_t dev); static int tegra124_pll_set_gate(struct clknode *clk, bool enable); static int tegra124_pll_recalc(struct clknode *clk, uint64_t *freq); static int tegra124_pll_set_freq(struct clknode *clknode, uint64_t fin, uint64_t *fout, int flags, int *stop); struct pll_sc { device_t clkdev; enum pll_type type; uint32_t base_reg; uint32_t misc_reg; uint32_t lock_mask; uint32_t lock_enable; uint32_t iddq_reg; uint32_t iddq_mask; uint32_t flags; struct pdiv_table *pdiv_table; struct mnp_bits mnp_bits; }; static clknode_method_t tegra124_pll_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, tegra124_pll_init), CLKNODEMETHOD(clknode_set_gate, tegra124_pll_set_gate), CLKNODEMETHOD(clknode_recalc_freq, tegra124_pll_recalc), CLKNODEMETHOD(clknode_set_freq, tegra124_pll_set_freq), CLKNODEMETHOD_END }; DEFINE_CLASS_1(tegra124_pll, tegra124_pll_class, tegra124_pll_methods, sizeof(struct pll_sc), clknode_class); static int pll_enable(struct pll_sc *sc) { uint32_t reg; RD4(sc, sc->base_reg, ®); if (sc->type != PLL_E) reg &= ~PLL_BASE_BYPASS; reg |= PLL_BASE_ENABLE; WR4(sc, sc->base_reg, reg); return (0); } static int pll_disable(struct pll_sc *sc) { uint32_t reg; RD4(sc, sc->base_reg, ®); if (sc->type != PLL_E) reg |= PLL_BASE_BYPASS; reg &= ~PLL_BASE_ENABLE; WR4(sc, sc->base_reg, reg); return (0); } static uint32_t pdiv_to_reg(struct pll_sc *sc, uint32_t p_div) { struct pdiv_table *tbl; tbl = sc->pdiv_table; if (tbl == NULL) return (ffs(p_div)); while (tbl->divider != 0) { if (p_div <= tbl->divider) return (tbl->value); tbl++; } return ~0; } static uint32_t reg_to_pdiv(struct pll_sc *sc, uint32_t reg) { struct pdiv_table *tbl; tbl = sc->pdiv_table; if (tbl != NULL) { while (tbl->divider) { if (reg == tbl->value) return (tbl->divider); tbl++; } return (0); } return (1 << reg); } static uint32_t get_masked(uint32_t val, uint32_t shift, uint32_t width) { return ((val >> shift) & ((1 << width) - 1)); } static uint32_t set_masked(uint32_t val, uint32_t v, uint32_t shift, uint32_t width) { val &= ~(((1 << width) - 1) << shift); val |= (v & ((1 << width) - 1)) << shift; return (val); } static void get_divisors(struct pll_sc *sc, uint32_t *m, uint32_t *n, uint32_t *p) { uint32_t val; struct mnp_bits *mnp_bits; mnp_bits = &sc->mnp_bits; RD4(sc, sc->base_reg, &val); *m = get_masked(val, PLL_BASE_DIVM_SHIFT, mnp_bits->m_width); *n = get_masked(val, PLL_BASE_DIVN_SHIFT, mnp_bits->n_width); *p = get_masked(val, mnp_bits->p_shift, mnp_bits->p_width); } static uint32_t set_divisors(struct pll_sc *sc, uint32_t val, uint32_t m, uint32_t n, uint32_t p) { struct mnp_bits *mnp_bits; mnp_bits = &sc->mnp_bits; val = set_masked(val, m, PLL_BASE_DIVM_SHIFT, mnp_bits->m_width); val = set_masked(val, n, PLL_BASE_DIVN_SHIFT, mnp_bits->n_width); val = set_masked(val, p, mnp_bits->p_shift, mnp_bits->p_width); return (val); } static bool is_locked(struct pll_sc *sc) { uint32_t reg; switch (sc->type) { case PLL_REFE: RD4(sc, sc->misc_reg, ®); reg &= PLLRE_MISC_LOCK; break; case PLL_E: RD4(sc, sc->misc_reg, ®); reg &= PLLE_MISC_LOCK; break; default: RD4(sc, sc->base_reg, ®); reg &= PLL_BASE_LOCK; break; } return (reg != 0); } static int wait_for_lock(struct pll_sc *sc) { int i; for (i = PLL_LOCK_TIMEOUT / 10; i > 0; i--) { if (is_locked(sc)) break; DELAY(10); } if (i <= 0) { printf("PLL lock timeout\n"); return (ETIMEDOUT); } return (0); } static int plle_enable(struct pll_sc *sc) { uint32_t reg; int rv; struct mnp_bits *mnp_bits; uint32_t pll_m = 1; uint32_t pll_n = 200; uint32_t pll_p = 13; uint32_t pll_cml = 13; mnp_bits = &sc->mnp_bits; /* Disable lock override. */ RD4(sc, sc->base_reg, ®); reg &= ~PLLE_BASE_LOCK_OVERRIDE; WR4(sc, sc->base_reg, reg); RD4(sc, PLLE_AUX, ®); reg |= PLLE_AUX_ENABLE_SWCTL; reg &= ~PLLE_AUX_SEQ_ENABLE; WR4(sc, PLLE_AUX, reg); DELAY(10); RD4(sc, sc->misc_reg, ®); reg |= PLLE_MISC_LOCK_ENABLE; reg |= PLLE_MISC_IDDQ_SWCTL; reg &= ~PLLE_MISC_IDDQ_OVERRIDE_VALUE; reg |= PLLE_MISC_PTS; reg |= PLLE_MISC_VREG_BG_CTRL_MASK; reg |= PLLE_MISC_VREG_CTRL_MASK; WR4(sc, sc->misc_reg, reg); DELAY(10); RD4(sc, PLLE_SS_CNTL, ®); reg |= PLLE_SS_CNTL_DISABLE; WR4(sc, PLLE_SS_CNTL, reg); RD4(sc, sc->base_reg, ®); reg = set_divisors(sc, reg, pll_m, pll_n, pll_p); reg &= ~(PLLE_BASE_DIVCML_MASK << PLLE_BASE_DIVCML_SHIFT); reg |= pll_cml << PLLE_BASE_DIVCML_SHIFT; WR4(sc, sc->base_reg, reg); DELAY(10); pll_enable(sc); rv = wait_for_lock(sc); if (rv != 0) return (rv); RD4(sc, PLLE_SS_CNTL, ®); reg &= ~PLLE_SS_CNTL_SSCCENTER; reg &= ~PLLE_SS_CNTL_SSCINVERT; reg &= ~PLLE_SS_CNTL_COEFFICIENTS_MASK; reg |= PLLE_SS_CNTL_COEFFICIENTS_VAL; WR4(sc, PLLE_SS_CNTL, reg); reg &= ~PLLE_SS_CNTL_SSCBYP; reg &= ~PLLE_SS_CNTL_BYPASS_SS; WR4(sc, PLLE_SS_CNTL, reg); DELAY(10); reg &= ~PLLE_SS_CNTL_INTERP_RESET; WR4(sc, PLLE_SS_CNTL, reg); DELAY(10); /* HW control of brick pll. */ RD4(sc, sc->misc_reg, ®); reg &= ~PLLE_MISC_IDDQ_SWCTL; WR4(sc, sc->misc_reg, reg); RD4(sc, PLLE_AUX, ®); reg |= PLLE_AUX_USE_LOCKDET; reg |= PLLE_AUX_SEQ_START_STATE; reg &= ~PLLE_AUX_ENABLE_SWCTL; reg &= ~PLLE_AUX_SS_SWCTL; WR4(sc, PLLE_AUX, reg); reg |= PLLE_AUX_SEQ_START_STATE; DELAY(10); reg |= PLLE_AUX_SEQ_ENABLE; WR4(sc, PLLE_AUX, reg); RD4(sc, XUSBIO_PLL_CFG0, ®); reg |= XUSBIO_PLL_CFG0_PADPLL_USE_LOCKDET; reg |= XUSBIO_PLL_CFG0_SEQ_START_STATE; reg &= ~XUSBIO_PLL_CFG0_CLK_ENABLE_SWCTL; reg &= ~XUSBIO_PLL_CFG0_PADPLL_RESET_SWCTL; WR4(sc, XUSBIO_PLL_CFG0, reg); DELAY(10); reg |= XUSBIO_PLL_CFG0_SEQ_ENABLE; WR4(sc, XUSBIO_PLL_CFG0, reg); /* Enable HW control and unreset SATA PLL. */ RD4(sc, SATA_PLL_CFG0, ®); reg &= ~SATA_PLL_CFG0_PADPLL_RESET_SWCTL; reg &= ~SATA_PLL_CFG0_PADPLL_RESET_OVERRIDE_VALUE; reg |= SATA_PLL_CFG0_PADPLL_USE_LOCKDET; reg &= ~SATA_PLL_CFG0_SEQ_IN_SWCTL; reg &= ~SATA_PLL_CFG0_SEQ_RESET_INPUT_VALUE; reg &= ~SATA_PLL_CFG0_SEQ_LANE_PD_INPUT_VALUE; reg &= ~SATA_PLL_CFG0_SEQ_PADPLL_PD_INPUT_VALUE; reg &= ~SATA_PLL_CFG0_SEQ_ENABLE; reg |= SATA_PLL_CFG0_SEQ_START_STATE; WR4(sc, SATA_PLL_CFG0, reg); DELAY(10); reg |= SATA_PLL_CFG0_SEQ_ENABLE; WR4(sc, SATA_PLL_CFG0, reg); /* Enable HW control of PCIe PLL. */ RD4(sc, PCIE_PLL_CFG0, ®); reg |= PCIE_PLL_CFG0_SEQ_ENABLE; WR4(sc, PCIE_PLL_CFG0, reg); return (0); } static int tegra124_pll_set_gate(struct clknode *clknode, bool enable) { int rv; struct pll_sc *sc; sc = clknode_get_softc(clknode); if (enable == 0) { rv = pll_disable(sc); return(rv); } if (sc->type == PLL_E) rv = plle_enable(sc); else rv = pll_enable(sc); return (rv); } static int pll_set_std(struct pll_sc *sc, uint64_t fin, uint64_t *fout, int flags, uint32_t m, uint32_t n, uint32_t p) { uint32_t reg; struct mnp_bits *mnp_bits; int rv; mnp_bits = &sc->mnp_bits; if (m >= (1 << mnp_bits->m_width)) return (ERANGE); if (n >= (1 << mnp_bits->n_width)) return (ERANGE); if (pdiv_to_reg(sc, p) >= (1 << mnp_bits->p_width)) return (ERANGE); if (flags & CLK_SET_DRYRUN) { if (((flags & (CLK_SET_ROUND_UP | CLK_SET_ROUND_DOWN)) == 0) && (*fout != (((fin / m) * n) /p))) return (ERANGE); *fout = ((fin / m) * n) /p; return (0); } pll_disable(sc); /* take pll out of IDDQ */ if (sc->iddq_reg != 0) MD4(sc, sc->iddq_reg, sc->iddq_mask, 0); RD4(sc, sc->base_reg, ®); reg = set_masked(reg, m, PLL_BASE_DIVM_SHIFT, mnp_bits->m_width); reg = set_masked(reg, n, PLL_BASE_DIVN_SHIFT, mnp_bits->n_width); reg = set_masked(reg, pdiv_to_reg(sc, p), mnp_bits->p_shift, mnp_bits->p_width); WR4(sc, sc->base_reg, reg); /* Enable PLL. */ RD4(sc, sc->base_reg, ®); reg |= PLL_BASE_ENABLE; WR4(sc, sc->base_reg, reg); /* Enable lock detection. */ RD4(sc, sc->misc_reg, ®); reg |= sc->lock_enable; WR4(sc, sc->misc_reg, reg); rv = wait_for_lock(sc); if (rv != 0) { /* Disable PLL */ RD4(sc, sc->base_reg, ®); reg &= ~PLL_BASE_ENABLE; WR4(sc, sc->base_reg, reg); return (rv); } RD4(sc, sc->misc_reg, ®); pll_enable(sc); *fout = ((fin / m) * n) / p; return 0; } static int plla_set_freq(struct pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { uint32_t m, n, p; p = 1; m = 5; n = (*fout * p * m + fin / 2)/ fin; dprintf("%s: m: %d, n: %d, p: %d\n", __func__, m, n, p); return (pll_set_std(sc, fin, fout, flags, m, n, p)); } static int pllc_set_freq(struct pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { uint32_t m, n, p; p = 2; m = 1; n = (*fout * p * m + fin / 2)/ fin; dprintf("%s: m: %d, n: %d, p: %d\n", __func__, m, n, p); return (pll_set_std( sc, fin, fout, flags, m, n, p)); } static int plld2_set_freq(struct pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { uint32_t m, n, p; p = 2; m = 1; n = (*fout * p * m + fin / 2)/ fin; dprintf("%s: m: %d, n: %d, p: %d\n", __func__, m, n, p); return (pll_set_std(sc, fin, fout, flags, m, n, p)); } static int pllrefe_set_freq(struct pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { uint32_t m, n, p; m = 1; p = 1; n = *fout * p * m / fin; return (pll_set_std(sc, fin, fout, flags, m, n, p)); } static int pllx_set_freq(struct pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { uint32_t reg; uint32_t m, n, p; struct mnp_bits *mnp_bits; int rv; mnp_bits = &sc->mnp_bits; p = 1; m = 1; n = (*fout * p * m + fin / 2)/ fin; dprintf("%s: m: %d, n: %d, p: %d\n", __func__, m, n, p); if (m >= (1 << mnp_bits->m_width)) return (ERANGE); if (n >= (1 << mnp_bits->n_width)) return (ERANGE); if (pdiv_to_reg(sc, p) >= (1 << mnp_bits->p_width)) return (ERANGE); if (flags & CLK_SET_DRYRUN) { if (((flags & (CLK_SET_ROUND_UP | CLK_SET_ROUND_DOWN)) == 0) && (*fout != (((fin / m) * n) /p))) return (ERANGE); *fout = ((fin / m) * n) /p; return (0); } - /* Set bypass. */ + /* PLLX doesn't have bypass, disable it first. */ RD4(sc, sc->base_reg, ®); - reg |= PLL_BASE_BYPASS; + reg &= ~PLL_BASE_ENABLE; WR4(sc, sc->base_reg, reg); - RD4(sc, sc->base_reg, ®); - DELAY(100); /* Set PLL. */ RD4(sc, sc->base_reg, ®); reg = set_masked(reg, m, PLL_BASE_DIVM_SHIFT, mnp_bits->m_width); reg = set_masked(reg, n, PLL_BASE_DIVN_SHIFT, mnp_bits->n_width); reg = set_masked(reg, pdiv_to_reg(sc, p), mnp_bits->p_shift, mnp_bits->p_width); WR4(sc, sc->base_reg, reg); RD4(sc, sc->base_reg, ®); DELAY(100); + /* Enable lock detection. */ + RD4(sc, sc->misc_reg, ®); + reg |= sc->lock_enable; + WR4(sc, sc->misc_reg, reg); + /* Enable PLL. */ RD4(sc, sc->base_reg, ®); reg |= PLL_BASE_ENABLE; WR4(sc, sc->base_reg, reg); - /* Enable lock detection */ - RD4(sc, sc->misc_reg, ®); - reg |= sc->lock_enable; - WR4(sc, sc->misc_reg, reg); - rv = wait_for_lock(sc); if (rv != 0) { /* Disable PLL */ RD4(sc, sc->base_reg, ®); reg &= ~PLL_BASE_ENABLE; WR4(sc, sc->base_reg, reg); return (rv); } RD4(sc, sc->misc_reg, ®); - /* Clear bypass. */ - RD4(sc, sc->base_reg, ®); - reg &= ~PLL_BASE_BYPASS; - WR4(sc, sc->base_reg, reg); *fout = ((fin / m) * n) / p; return (0); } static int tegra124_pll_set_freq(struct clknode *clknode, uint64_t fin, uint64_t *fout, int flags, int *stop) { *stop = 1; int rv; struct pll_sc *sc; sc = clknode_get_softc(clknode); dprintf("%s: Requested freq: %llu, input freq: %llu\n", __func__, *fout, fin); switch (sc->type) { case PLL_A: rv = plla_set_freq(sc, fin, fout, flags); break; case PLL_C: rv = pllc_set_freq(sc, fin, fout, flags); break; case PLL_D2: rv = plld2_set_freq(sc, fin, fout, flags); break; case PLL_REFE: rv = pllrefe_set_freq(sc, fin, fout, flags); break; case PLL_X: rv = pllx_set_freq(sc, fin, fout, flags); break; case PLL_U: if (*fout == 480000000) /* PLLU is fixed to 480 MHz */ rv = 0; else rv = ERANGE; break; default: rv = ENXIO; break; } return (rv); } static int tegra124_pll_init(struct clknode *clk, device_t dev) { struct pll_sc *sc; uint32_t reg; sc = clknode_get_softc(clk); /* If PLL is enabled, enable lock detect too. */ RD4(sc, sc->base_reg, ®); if (reg & PLL_BASE_ENABLE) { RD4(sc, sc->misc_reg, ®); reg |= sc->lock_enable; WR4(sc, sc->misc_reg, reg); } clknode_init_parent_idx(clk, 0); return(0); } static int tegra124_pll_recalc(struct clknode *clk, uint64_t *freq) { struct pll_sc *sc; uint32_t m, n, p, pr; uint32_t reg, misc_reg; int locked; sc = clknode_get_softc(clk); RD4(sc, sc->base_reg, ®); RD4(sc, sc->misc_reg, &misc_reg); get_divisors(sc, &m, &n, &pr); if (sc->type != PLL_E) p = reg_to_pdiv(sc, pr); else p = 2 * (pr - 1); locked = is_locked(sc); dprintf("%s: %s (0x%08x, 0x%08x) - m: %d, n: %d, p: %d (%d): " "e: %d, r: %d, o: %d - %s\n", __func__, clknode_get_name(clk), reg, misc_reg, m, n, p, pr, (reg >> 30) & 1, (reg >> 29) & 1, (reg >> 28) & 1, locked ? "locked" : "unlocked"); if ((m == 0) || (n == 0) || (p == 0)) { *freq = 0; return (EINVAL); } *freq = ((*freq / m) * n) / p; return (0); } static int pll_register(struct clkdom *clkdom, struct clk_pll_def *clkdef) { struct clknode *clk; struct pll_sc *sc; clk = clknode_create(clkdom, &tegra124_pll_class, &clkdef->clkdef); if (clk == NULL) return (ENXIO); sc = clknode_get_softc(clk); sc->clkdev = clknode_get_device(clk); sc->type = clkdef->type; sc->base_reg = clkdef->base_reg; sc->misc_reg = clkdef->misc_reg; sc->lock_mask = clkdef->lock_mask; sc->lock_enable = clkdef->lock_enable; sc->iddq_reg = clkdef->iddq_reg; sc->iddq_mask = clkdef->iddq_mask; sc->flags = clkdef->flags; sc->pdiv_table = clkdef->pdiv_table; sc->mnp_bits = clkdef->mnp_bits; clknode_register(clkdom, clk); return (0); } static void config_utmi_pll(struct tegra124_car_softc *sc) { uint32_t reg; /* * XXX Simplified UTMIP settings for 12MHz base clock. */ #define ENABLE_DELAY_COUNT 0x02 #define STABLE_COUNT 0x2F #define ACTIVE_DELAY_COUNT 0x04 #define XTAL_FREQ_COUNT 0x76 CLKDEV_READ_4(sc->dev, UTMIP_PLL_CFG2, ®); reg &= ~UTMIP_PLL_CFG2_STABLE_COUNT(~0); reg |= UTMIP_PLL_CFG2_STABLE_COUNT(STABLE_COUNT); reg &= ~UTMIP_PLL_CFG2_ACTIVE_DLY_COUNT(~0); reg |= UTMIP_PLL_CFG2_ACTIVE_DLY_COUNT(ACTIVE_DELAY_COUNT); reg &= ~UTMIP_PLL_CFG2_FORCE_PD_SAMP_A_POWERDOWN; reg &= ~UTMIP_PLL_CFG2_FORCE_PD_SAMP_B_POWERDOWN; reg &= ~UTMIP_PLL_CFG2_FORCE_PD_SAMP_C_POWERDOWN; CLKDEV_WRITE_4(sc->dev, UTMIP_PLL_CFG2, reg); CLKDEV_READ_4(sc->dev, UTMIP_PLL_CFG1, ®); reg &= ~UTMIP_PLL_CFG1_ENABLE_DLY_COUNT(~0); reg |= UTMIP_PLL_CFG1_ENABLE_DLY_COUNT(ENABLE_DELAY_COUNT); reg &= ~UTMIP_PLL_CFG1_XTAL_FREQ_COUNT(~0); reg |= UTMIP_PLL_CFG1_XTAL_FREQ_COUNT(XTAL_FREQ_COUNT); reg &= ~UTMIP_PLL_CFG1_FORCE_PLL_ENABLE_POWERDOWN; reg &= ~UTMIP_PLL_CFG1_FORCE_PLL_ACTIVE_POWERDOWN; reg &= ~UTMIP_PLL_CFG1_FORCE_PLLU_POWERUP; reg &= ~UTMIP_PLL_CFG1_FORCE_PLLU_POWERDOWN; CLKDEV_WRITE_4(sc->dev, UTMIP_PLL_CFG1, reg); /* Prepare UTMIP requencer. */ CLKDEV_READ_4(sc->dev, UTMIPLL_HW_PWRDN_CFG0, ®); reg |= UTMIPLL_HW_PWRDN_CFG0_USE_LOCKDET; reg &= ~UTMIPLL_HW_PWRDN_CFG0_CLK_ENABLE_SWCTL; reg |= UTMIPLL_HW_PWRDN_CFG0_SEQ_START_STATE; CLKDEV_WRITE_4(sc->dev, UTMIPLL_HW_PWRDN_CFG0, reg); /* Powerup UTMIP. */ CLKDEV_READ_4(sc->dev, UTMIP_PLL_CFG1, ®); reg &= ~UTMIP_PLL_CFG1_FORCE_PLL_ENABLE_POWERUP; reg &= ~UTMIP_PLL_CFG1_FORCE_PLL_ENABLE_POWERDOWN; CLKDEV_WRITE_4(sc->dev, UTMIP_PLL_CFG1, reg); DELAY(10); /* SW override for UTMIPLL */ CLKDEV_READ_4(sc->dev, UTMIPLL_HW_PWRDN_CFG0, ®); reg |= UTMIPLL_HW_PWRDN_CFG0_IDDQ_SWCTL; reg &= ~UTMIPLL_HW_PWRDN_CFG0_IDDQ_OVERRIDE; CLKDEV_WRITE_4(sc->dev, UTMIPLL_HW_PWRDN_CFG0, reg); DELAY(10); /* HW control of UTMIPLL. */ CLKDEV_READ_4(sc->dev, UTMIPLL_HW_PWRDN_CFG0, ®); reg |= UTMIPLL_HW_PWRDN_CFG0_SEQ_ENABLE; CLKDEV_WRITE_4(sc->dev, UTMIPLL_HW_PWRDN_CFG0, reg); } void tegra124_init_plls(struct tegra124_car_softc *sc) { int i, rv; for (i = 0; i < nitems(pll_clks); i++) { rv = pll_register(sc->clkdom, pll_clks + i); if (rv != 0) panic("pll_register failed"); } config_utmi_pll(sc); } Index: projects/release-pkg/sys/arm/nvidia/tegra124/tegra124_clk_super.c =================================================================== --- projects/release-pkg/sys/arm/nvidia/tegra124/tegra124_clk_super.c (revision 297604) +++ projects/release-pkg/sys/arm/nvidia/tegra124/tegra124_clk_super.c (revision 297605) @@ -1,265 +1,265 @@ /*- * 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$"); #include #include #include #include #include #include #include #include #include #include "tegra124_car.h" /* Flags */ #define SMF_HAVE_DIVIDER_2 1 struct super_mux_def { struct clknode_init_def clkdef; uint32_t base_reg; uint32_t flags; int src_pllx; int src_div2; }; #define PLIST(x) static const char *x[] #define SM(_id, cn, pl, r, x, d, f) \ { \ .clkdef.id = _id, \ .clkdef.name = cn, \ .clkdef.parent_names = pl, \ .clkdef.parent_cnt = nitems(pl), \ .clkdef.flags = CLK_NODE_STATIC_STRINGS, \ .base_reg = r, \ .src_pllx = x, \ .src_div2 = d, \ .flags = f, \ } PLIST(cclk_g_parents) = { "clk_m", "pllC_out0", "clk_s", "pllM_out0", "pllP_out0", "pllP_out4", "pllC2_out0", "pllC3_out0", "pllX_out", NULL, NULL, NULL, NULL, NULL, NULL,NULL, // "dfllCPU_out0" }; PLIST(cclk_lp_parents) = { "clk_m", "pllC_out0", "clk_s", "pllM_out0", "pllP_out0", "pllP_out4", "pllC2_out0", "pllC3_out0", "pllX_out", NULL, NULL, NULL, NULL, NULL, NULL, NULL, "pllX_out0" }; PLIST(sclk_parents) = { "clk_m", "pllC_out1", "pllP_out4", "pllP_out0", "pllP_out2", "pllC_out0", "clk_s", "pllM_out1", }; static struct super_mux_def super_mux_def[] = { SM(TEGRA124_CLK_CCLK_G, "cclk_g", cclk_g_parents, CCLKG_BURST_POLICY, 0, 0, 0), SM(TEGRA124_CLK_CCLK_LP, "cclk_lp", cclk_lp_parents, CCLKLP_BURST_POLICY, 8, 16, SMF_HAVE_DIVIDER_2), SM(TEGRA124_CLK_SCLK, "sclk", sclk_parents, SCLK_BURST_POLICY, 0, 0, 0), }; static int super_mux_init(struct clknode *clk, device_t dev); static int super_mux_set_mux(struct clknode *clk, int idx); struct super_mux_sc { device_t clkdev; uint32_t base_reg; int src_pllx; int src_div2; uint32_t flags; int mux; }; static clknode_method_t super_mux_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, super_mux_init), CLKNODEMETHOD(clknode_set_mux, super_mux_set_mux), CLKNODEMETHOD_END }; DEFINE_CLASS_1(tegra124_super_mux, tegra124_super_mux_class, super_mux_methods, sizeof(struct super_mux_sc), clknode_class); /* Mux status. */ #define SUPER_MUX_STATE_STDBY 0 #define SUPER_MUX_STATE_IDLE 1 #define SUPER_MUX_STATE_RUN 2 #define SUPER_MUX_STATE_IRQ 3 #define SUPER_MUX_STATE_FIQ 4 /* Mux register bits. */ #define SUPER_MUX_STATE_BIT_SHIFT 28 #define SUPER_MUX_STATE_BIT_MASK 0xF /* State is Priority encoded */ #define SUPER_MUX_STATE_BIT_STDBY 0x00 #define SUPER_MUX_STATE_BIT_IDLE 0x01 #define SUPER_MUX_STATE_BIT_RUN 0x02 #define SUPER_MUX_STATE_BIT_IRQ 0x04 #define SUPER_MUX_STATE_BIT_FIQ 0x08 #define SUPER_MUX_MUX_WIDTH 4 #define SUPER_MUX_LP_DIV2_BYPASS (1 << 16) static uint32_t super_mux_get_state(uint32_t reg) { reg = (reg >> SUPER_MUX_STATE_BIT_SHIFT) & SUPER_MUX_STATE_BIT_MASK; if (reg & SUPER_MUX_STATE_BIT_FIQ) return (SUPER_MUX_STATE_FIQ); if (reg & SUPER_MUX_STATE_BIT_IRQ) return (SUPER_MUX_STATE_IRQ); if (reg & SUPER_MUX_STATE_BIT_RUN) return (SUPER_MUX_STATE_RUN); if (reg & SUPER_MUX_STATE_BIT_IDLE) return (SUPER_MUX_STATE_IDLE); return (SUPER_MUX_STATE_STDBY); } static int super_mux_init(struct clknode *clk, device_t dev) { struct super_mux_sc *sc; uint32_t reg; int shift, state; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); RD4(sc, sc->base_reg, ®); DEVICE_UNLOCK(sc); state = super_mux_get_state(reg); if ((state != SUPER_MUX_STATE_RUN) && (state != SUPER_MUX_STATE_IDLE)) { panic("Unexpected super mux state: %u", state); } shift = state * SUPER_MUX_MUX_WIDTH; sc->mux = (reg >> shift) & ((1 << SUPER_MUX_MUX_WIDTH) - 1); /* * CCLKLP uses PLLX/2 as source if LP_DIV2_BYPASS isn't set * and source mux is set to PLLX. */ if (sc->flags & SMF_HAVE_DIVIDER_2) { if (((reg & SUPER_MUX_LP_DIV2_BYPASS) == 0) && (sc->mux == sc->src_pllx)) sc->mux = sc->src_div2; } clknode_init_parent_idx(clk, sc->mux); return(0); } static int super_mux_set_mux(struct clknode *clk, int idx) { struct super_mux_sc *sc; int shift, state; uint32_t reg, dummy; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); RD4(sc, sc->base_reg, ®); state = super_mux_get_state(reg); if ((state != SUPER_MUX_STATE_RUN) && (state != SUPER_MUX_STATE_IDLE)) { panic("Unexpected super mux state: %u", state); } - - shift = state * SUPER_MUX_MUX_WIDTH; + shift = (state - 1) * SUPER_MUX_MUX_WIDTH; sc->mux = idx; if (sc->flags & SMF_HAVE_DIVIDER_2) { if (idx == sc->src_div2) { idx = sc->src_pllx; reg &= ~SUPER_MUX_LP_DIV2_BYPASS; WR4(sc, sc->base_reg, reg); RD4(sc, sc->base_reg, &dummy); } else if (idx == sc->src_pllx) { reg = SUPER_MUX_LP_DIV2_BYPASS; WR4(sc, sc->base_reg, reg); RD4(sc, sc->base_reg, &dummy); } } reg &= ~(((1 << SUPER_MUX_MUX_WIDTH) - 1) << shift); reg |= idx << shift; + WR4(sc, sc->base_reg, reg); RD4(sc, sc->base_reg, &dummy); DEVICE_UNLOCK(sc); return(0); } static int super_mux_register(struct clkdom *clkdom, struct super_mux_def *clkdef) { struct clknode *clk; struct super_mux_sc *sc; clk = clknode_create(clkdom, &tegra124_super_mux_class, &clkdef->clkdef); if (clk == NULL) return (1); sc = clknode_get_softc(clk); sc->clkdev = clknode_get_device(clk); sc->base_reg = clkdef->base_reg; sc->src_pllx = clkdef->src_pllx; sc->src_div2 = clkdef->src_div2; sc->flags = clkdef->flags; clknode_register(clkdom, clk); return (0); } void tegra124_super_mux_clock(struct tegra124_car_softc *sc) { int i, rv; for (i = 0; i < nitems(super_mux_def); i++) { rv = super_mux_register(sc->clkdom, &super_mux_def[i]); if (rv != 0) panic("super_mux_register failed"); } } Index: projects/release-pkg/sys/arm/nvidia/tegra124/tegra124_cpufreq.c =================================================================== --- projects/release-pkg/sys/arm/nvidia/tegra124/tegra124_cpufreq.c (revision 297604) +++ projects/release-pkg/sys/arm/nvidia/tegra124/tegra124_cpufreq.c (revision 297605) @@ -1,583 +1,598 @@ /*- * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #define XXX /* CPU voltage table entry */ struct speedo_entry { uint64_t freq; /* Frequency point */ int c0; /* Coeeficient values for */ int c1; /* quadratic equation: */ int c2; /* c2 * speedo^2 + c1 * speedo + c0 */ }; struct cpu_volt_def { int min_uvolt; /* Min allowed CPU voltage */ int max_uvolt; /* Max allowed CPU voltage */ int step_uvolt; /* Step of CPU voltage */ int speedo_scale; /* Scaling factor for cvt */ int speedo_nitems; /* Size of speedo table */ struct speedo_entry *speedo_tbl; /* CPU voltage table */ }; struct cpu_speed_point { uint64_t freq; /* Frequecy */ int uvolt; /* Requested voltage */ }; static struct speedo_entry tegra124_speedo_dpll_tbl[] = { { 204000000ULL, 1112619, -29295, 402}, { 306000000ULL, 1150460, -30585, 402}, { 408000000ULL, 1190122, -31865, 402}, { 510000000ULL, 1231606, -33155, 402}, { 612000000ULL, 1274912, -34435, 402}, { 714000000ULL, 1320040, -35725, 402}, { 816000000ULL, 1366990, -37005, 402}, { 918000000ULL, 1415762, -38295, 402}, {1020000000ULL, 1466355, -39575, 402}, {1122000000ULL, 1518771, -40865, 402}, {1224000000ULL, 1573009, -42145, 402}, {1326000000ULL, 1629068, -43435, 402}, {1428000000ULL, 1686950, -44715, 402}, {1530000000ULL, 1746653, -46005, 402}, {1632000000ULL, 1808179, -47285, 402}, {1734000000ULL, 1871526, -48575, 402}, {1836000000ULL, 1936696, -49855, 402}, {1938000000ULL, 2003687, -51145, 402}, {2014500000ULL, 2054787, -52095, 402}, {2116500000ULL, 2124957, -53385, 402}, {2218500000ULL, 2196950, -54665, 402}, {2320500000ULL, 2270765, -55955, 402}, {2320500000ULL, 2270765, -55955, 402}, {2422500000ULL, 2346401, -57235, 402}, {2524500000ULL, 2437299, -58535, 402}, }; static struct cpu_volt_def tegra124_cpu_volt_dpll_def = { .min_uvolt = 900000, /* 0.9 V */ .max_uvolt = 1260000, /* 1.26 */ .step_uvolt = 10000, /* 10 mV */ .speedo_scale = 100, .speedo_nitems = nitems(tegra124_speedo_dpll_tbl), .speedo_tbl = tegra124_speedo_dpll_tbl, }; static struct speedo_entry tegra124_speedo_pllx_tbl[] = { { 204000000ULL, 800000, 0, 0}, { 306000000ULL, 800000, 0, 0}, { 408000000ULL, 800000, 0, 0}, { 510000000ULL, 800000, 0, 0}, { 612000000ULL, 800000, 0, 0}, { 714000000ULL, 800000, 0, 0}, { 816000000ULL, 820000, 0, 0}, { 918000000ULL, 840000, 0, 0}, {1020000000ULL, 880000, 0, 0}, {1122000000ULL, 900000, 0, 0}, {1224000000ULL, 930000, 0, 0}, {1326000000ULL, 960000, 0, 0}, {1428000000ULL, 990000, 0, 0}, {1530000000ULL, 1020000, 0, 0}, {1632000000ULL, 1070000, 0, 0}, {1734000000ULL, 1100000, 0, 0}, {1836000000ULL, 1140000, 0, 0}, {1938000000ULL, 1180000, 0, 0}, {2014500000ULL, 1220000, 0, 0}, {2116500000ULL, 1260000, 0, 0}, {2218500000ULL, 1310000, 0, 0}, {2320500000ULL, 1360000, 0, 0}, {2397000000ULL, 1400000, 0, 0}, {2499000000ULL, 1400000, 0, 0}, }; static struct cpu_volt_def tegra124_cpu_volt_pllx_def = { .min_uvolt = 900000, /* 0.9 V */ .max_uvolt = 1260000, /* 1.26 */ .step_uvolt = 10000, /* 10 mV */ .speedo_scale = 100, .speedo_nitems = nitems(tegra124_speedo_pllx_tbl), .speedo_tbl = tegra124_speedo_pllx_tbl, }; static uint64_t cpu_freq_tbl[] = { 204000000ULL, 306000000ULL, 408000000ULL, 510000000ULL, 612000000ULL, 714000000ULL, 816000000ULL, 918000000ULL, 1020000000ULL, 1122000000ULL, 1224000000ULL, 1326000000ULL, 1428000000ULL, 1530000000ULL, 1632000000ULL, 1734000000ULL, 1836000000ULL, 1938000000ULL, 2014000000ULL, 2116000000ULL, 2218000000ULL, 2320000000ULL, 2320000000ULL, 2422000000ULL, 2524000000ULL, }; static uint64_t cpu_max_freq[] = { 2014500000ULL, 2320500000ULL, 2116500000ULL, 2524500000ULL, }; struct tegra124_cpufreq_softc { device_t dev; phandle_t node; regulator_t supply_vdd_cpu; clk_t clk_cpu_g; clk_t clk_cpu_lp; clk_t clk_pll_x; clk_t clk_pll_p; clk_t clk_dfll; int process_id; int speedo_id; int speedo_value; uint64_t cpu_max_freq; struct cpu_volt_def *cpu_def; struct cpu_speed_point *speed_points; int nspeed_points; struct cpu_speed_point *act_speed_point; int latency; }; static int cpufreq_lowest_freq = 1; TUNABLE_INT("hw.tegra124.cpufreq.lowest_freq", &cpufreq_lowest_freq); #define DIV_ROUND_CLOSEST(val, div) (((val) + ((div) / 2)) / (div)) #define ROUND_UP(val, div) ((((val) + ((div) - 1)) / (div)) * (div)) #define ROUND_DOWN(val, div) (((val) / (div)) * (div)) /* * Compute requesetd voltage for given frequency and SoC process variations, * - compute base voltage from speedo value using speedo table * - round up voltage to next regulator step * - clamp it to regulator limits */ static int freq_to_voltage(struct tegra124_cpufreq_softc *sc, uint64_t freq) { int uv, scale, min_uvolt, max_uvolt, step_uvolt; struct speedo_entry *ent; int i; /* Get speedo entry with higher frequency */ ent = NULL; for (i = 0; i < sc->cpu_def->speedo_nitems; i++) { if (sc->cpu_def->speedo_tbl[i].freq >= freq) { ent = &sc->cpu_def->speedo_tbl[i]; break; } } if (ent == NULL) ent = &sc->cpu_def->speedo_tbl[sc->cpu_def->speedo_nitems - 1]; scale = sc->cpu_def->speedo_scale; /* uV = (c2 * speedo / scale + c1) * speedo / scale + c0) */ uv = DIV_ROUND_CLOSEST(ent->c2 * sc->speedo_value, scale); uv = DIV_ROUND_CLOSEST((uv + ent->c1) * sc->speedo_value, scale) + ent->c0; step_uvolt = sc->cpu_def->step_uvolt; /* Round up it to next regulator step */ uv = ROUND_UP(uv, step_uvolt); /* Clamp result */ min_uvolt = ROUND_UP(sc->cpu_def->min_uvolt, step_uvolt); max_uvolt = ROUND_DOWN(sc->cpu_def->max_uvolt, step_uvolt); if (uv < min_uvolt) uv = min_uvolt; if (uv > max_uvolt) uv = max_uvolt; return (uv); } static void build_speed_points(struct tegra124_cpufreq_softc *sc) { int i; sc->nspeed_points = nitems(cpu_freq_tbl); sc->speed_points = malloc(sizeof(struct cpu_speed_point) * sc->nspeed_points, M_DEVBUF, M_NOWAIT); for (i = 0; i < sc->nspeed_points; i++) { sc->speed_points[i].freq = cpu_freq_tbl[i]; sc->speed_points[i].uvolt = freq_to_voltage(sc, cpu_freq_tbl[i]); } } static struct cpu_speed_point * get_speed_point(struct tegra124_cpufreq_softc *sc, uint64_t freq) { int i; if (sc->speed_points[0].freq >= freq) return (sc->speed_points + 0); for (i = 0; i < sc->nspeed_points - 1; i++) { if (sc->speed_points[i + 1].freq > freq) return (sc->speed_points + i); } return (sc->speed_points + sc->nspeed_points - 1); } static int tegra124_cpufreq_settings(device_t dev, struct cf_setting *sets, int *count) { struct tegra124_cpufreq_softc *sc; int i, j, max_cnt; if (sets == NULL || count == NULL) return (EINVAL); sc = device_get_softc(dev); memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * (*count)); max_cnt = min(sc->nspeed_points, *count); for (i = 0, j = sc->nspeed_points - 1; j >= 0; j--) { if (sc->cpu_max_freq < sc->speed_points[j].freq) continue; sets[i].freq = sc->speed_points[j].freq / 1000000; sets[i].volts = sc->speed_points[j].uvolt / 1000; sets[i].lat = sc->latency; sets[i].dev = dev; i++; } *count = i; return (0); } static int set_cpu_freq(struct tegra124_cpufreq_softc *sc, uint64_t freq) { struct cpu_speed_point *point; int rv; point = get_speed_point(sc, freq); if (sc->act_speed_point->uvolt < point->uvolt) { /* set cpu voltage */ rv = regulator_set_voltage(sc->supply_vdd_cpu, point->uvolt, point->uvolt); DELAY(10000); if (rv != 0) return (rv); } - rv = clk_set_freq(sc->clk_cpu_g, point->freq, CLK_SET_ROUND_DOWN); + + /* Switch supermux to PLLP first */ + rv = clk_set_parent_by_clk(sc->clk_cpu_g, sc->clk_pll_p); if (rv != 0) { + device_printf(sc->dev, "Can't set parent to PLLP\n"); + return (rv); + } + + /* Set PLLX frequency */ + rv = clk_set_freq(sc->clk_pll_x, point->freq, CLK_SET_ROUND_DOWN); + if (rv != 0) { device_printf(sc->dev, "Can't set CPU clock frequency\n"); + return (rv); + } + + rv = clk_set_parent_by_clk(sc->clk_cpu_g, sc->clk_pll_x); + if (rv != 0) { + device_printf(sc->dev, "Can't set parent to PLLX\n"); return (rv); } if (sc->act_speed_point->uvolt > point->uvolt) { /* set cpu voltage */ rv = regulator_set_voltage(sc->supply_vdd_cpu, point->uvolt, point->uvolt); if (rv != 0) return (rv); } sc->act_speed_point = point; return (0); } static int tegra124_cpufreq_set(device_t dev, const struct cf_setting *cf) { struct tegra124_cpufreq_softc *sc; uint64_t freq; int rv; if (cf == NULL || cf->freq < 0) return (EINVAL); sc = device_get_softc(dev); freq = cf->freq; if (freq < cpufreq_lowest_freq) freq = cpufreq_lowest_freq; freq *= 1000000; if (freq >= sc->cpu_max_freq) freq = sc->cpu_max_freq; rv = set_cpu_freq(sc, freq); return (rv); } static int tegra124_cpufreq_get(device_t dev, struct cf_setting *cf) { struct tegra124_cpufreq_softc *sc; if (cf == NULL) return (EINVAL); sc = device_get_softc(dev); memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf)); cf->dev = NULL; cf->freq = sc->act_speed_point->freq / 1000000; cf->volts = sc->act_speed_point->uvolt / 1000; /* Transition latency in us. */ cf->lat = sc->latency; /* Driver providing this setting. */ cf->dev = dev; return (0); } static int tegra124_cpufreq_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } static int get_fdt_resources(struct tegra124_cpufreq_softc *sc, phandle_t node) { int rv; device_t parent_dev; parent_dev = device_get_parent(sc->dev); rv = regulator_get_by_ofw_property(parent_dev, "vdd-cpu-supply", &sc->supply_vdd_cpu); if (rv != 0) { device_printf(sc->dev, "Cannot get 'vdd-cpu' regulator\n"); return (rv); } rv = clk_get_by_ofw_name(parent_dev, "cpu_g", &sc->clk_cpu_g); if (rv != 0) { device_printf(sc->dev, "Cannot get 'cpu_g' clock: %d\n", rv); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, "cpu_lp", &sc->clk_cpu_lp); if (rv != 0) { device_printf(sc->dev, "Cannot get 'cpu_lp' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, "pll_x", &sc->clk_pll_x); if (rv != 0) { device_printf(sc->dev, "Cannot get 'pll_x' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, "pll_p", &sc->clk_pll_p); if (rv != 0) { device_printf(parent_dev, "Cannot get 'pll_p' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, "dfll", &sc->clk_dfll); if (rv != 0) { /* XXX DPLL is not implemented yet */ /* device_printf(sc->dev, "Cannot get 'dfll' clock\n"); return (ENXIO); */ } return (0); } static void tegra124_cpufreq_identify(driver_t *driver, device_t parent) { if (device_find_child(parent, "tegra124_cpufreq", -1) != NULL) return; if (BUS_ADD_CHILD(parent, 0, "tegra124_cpufreq", -1) == NULL) device_printf(parent, "add child failed\n"); } static int tegra124_cpufreq_probe(device_t dev) { if (device_get_unit(dev) != 0) return (ENXIO); device_set_desc(dev, "CPU Frequency Control"); return (0); } static int tegra124_cpufreq_attach(device_t dev) { struct tegra124_cpufreq_softc *sc; uint64_t freq; int rv; sc = device_get_softc(dev); sc->dev = dev; sc->node = ofw_bus_get_node(device_get_parent(dev)); sc->process_id = tegra_sku_info.cpu_process_id; sc->speedo_id = tegra_sku_info.cpu_speedo_id; sc->speedo_value = tegra_sku_info.cpu_speedo_value; /* Tegra 124 */ /* XXX DPLL is not implemented yet */ if (1) sc->cpu_def = &tegra124_cpu_volt_pllx_def; else sc->cpu_def = &tegra124_cpu_volt_dpll_def; rv = get_fdt_resources(sc, sc->node); if (rv != 0) { return (rv); } build_speed_points(sc); rv = clk_get_freq(sc->clk_cpu_g, &freq); if (rv != 0) { device_printf(dev, "Can't get CPU clock frequency\n"); return (rv); } if (sc->speedo_id < nitems(cpu_max_freq)) sc->cpu_max_freq = cpu_max_freq[sc->speedo_id]; else sc->cpu_max_freq = cpu_max_freq[0]; sc->act_speed_point = get_speed_point(sc, freq); /* Set safe startup CPU frequency. */ rv = set_cpu_freq(sc, 1632000000); if (rv != 0) { device_printf(dev, "Can't set initial CPU clock frequency\n"); return (rv); } /* This device is controlled by cpufreq(4). */ cpufreq_register(dev); return (0); } static int tegra124_cpufreq_detach(device_t dev) { struct tegra124_cpufreq_softc *sc; sc = device_get_softc(dev); cpufreq_unregister(dev); if (sc->supply_vdd_cpu != NULL) regulator_release(sc->supply_vdd_cpu); if (sc->clk_cpu_g != NULL) clk_release(sc->clk_cpu_g); if (sc->clk_cpu_lp != NULL) clk_release(sc->clk_cpu_lp); if (sc->clk_pll_x != NULL) clk_release(sc->clk_pll_x); if (sc->clk_pll_p != NULL) clk_release(sc->clk_pll_p); if (sc->clk_dfll != NULL) clk_release(sc->clk_dfll); return (0); } static device_method_t tegra124_cpufreq_methods[] = { /* Device interface */ DEVMETHOD(device_identify, tegra124_cpufreq_identify), DEVMETHOD(device_probe, tegra124_cpufreq_probe), DEVMETHOD(device_attach, tegra124_cpufreq_attach), DEVMETHOD(device_detach, tegra124_cpufreq_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, tegra124_cpufreq_set), DEVMETHOD(cpufreq_drv_get, tegra124_cpufreq_get), DEVMETHOD(cpufreq_drv_settings, tegra124_cpufreq_settings), DEVMETHOD(cpufreq_drv_type, tegra124_cpufreq_type), DEVMETHOD_END }; static devclass_t tegra124_cpufreq_devclass; static driver_t tegra124_cpufreq_driver = { "tegra124_cpufreq", tegra124_cpufreq_methods, sizeof(struct tegra124_cpufreq_softc), }; DRIVER_MODULE(tegra124_cpufreq, cpu, tegra124_cpufreq_driver, tegra124_cpufreq_devclass, 0, 0); Index: projects/release-pkg/sys/arm/nvidia/tegra_ehci.c =================================================================== --- projects/release-pkg/sys/arm/nvidia/tegra_ehci.c (revision 297604) +++ projects/release-pkg/sys/arm/nvidia/tegra_ehci.c (revision 297605) @@ -1,322 +1,322 @@ /*- * 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$"); /* * EHCI driver for Tegra SoCs. */ #include "opt_bus.h" #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 "usbdevs.h" #define TEGRA_EHCI_REG_OFF 0x100 #define TEGRA_EHCI_REG_SIZE 0x100 /* Compatible devices. */ #define TEGRA124_EHCI 1 static struct ofw_compat_data compat_data[] = { {"nvidia,tegra124-ehci", (uintptr_t)TEGRA124_EHCI}, {NULL, 0}, }; struct tegra_ehci_softc { ehci_softc_t ehci_softc; device_t dev; struct resource *ehci_mem_res; /* EHCI core regs. */ struct resource *ehci_irq_res; /* EHCI core IRQ. */ int usb_alloc_called; clk_t clk; phy_t phy; hwreset_t reset; }; static void tegra_ehci_post_reset(struct ehci_softc *ehci_softc) { uint32_t usbmode; /* Force HOST mode. */ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_LPM); usbmode &= ~EHCI_UM_CM; usbmode |= EHCI_UM_CM_HOST; device_printf(ehci_softc->sc_bus.bdev, "set host controller mode\n"); EOWRITE4(ehci_softc, EHCI_USBMODE_LPM, usbmode); } static int tegra_ehci_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, "Nvidia Tegra EHCI controller"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int tegra_ehci_detach(device_t dev) { struct tegra_ehci_softc *sc; ehci_softc_t *esc; sc = device_get_softc(dev); esc = &sc->ehci_softc; if (sc->clk != NULL) clk_release(sc->clk); if (esc->sc_bus.bdev != NULL) device_delete_child(dev, esc->sc_bus.bdev); if (esc->sc_flags & EHCI_SCFLG_DONEINIT) ehci_detach(esc); if (esc->sc_intr_hdl != NULL) bus_teardown_intr(dev, esc->sc_irq_res, esc->sc_intr_hdl); if (sc->ehci_irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->ehci_irq_res); if (sc->ehci_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->ehci_mem_res); if (sc->usb_alloc_called) usb_bus_mem_free_all(&esc->sc_bus, &ehci_iterate_hw_softc); /* During module unload there are lots of children leftover. */ device_delete_children(dev); return (0); } static int tegra_ehci_attach(device_t dev) { struct tegra_ehci_softc *sc; ehci_softc_t *esc; int rv, rid; uint64_t freq; phandle_t node; sc = device_get_softc(dev); sc->dev = dev; node = ofw_bus_get_node(dev); esc = &sc->ehci_softc; /* Allocate resources. */ rid = 0; sc->ehci_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE | RF_SHAREABLE); if (sc->ehci_mem_res == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); rv = ENXIO; goto out; } rid = 0; sc->ehci_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->ehci_irq_res == NULL) { device_printf(dev, "Cannot allocate IRQ resources\n"); rv = ENXIO; goto out; } rv = hwreset_get_by_ofw_name(dev, "usb", &sc->reset); if (rv != 0) { device_printf(dev, "Cannot get reset\n"); rv = ENXIO; goto out; } rv = phy_get_by_ofw_property(sc->dev, "nvidia,phy", &sc->phy); if (rv != 0) { device_printf(sc->dev, "Cannot get 'nvidia,phy' phy\n"); rv = ENXIO; goto out; } rv = clk_get_by_ofw_index(sc->dev, 0, &sc->clk); if (rv != 0) { device_printf(dev, "Cannot get clock\n"); goto out; } rv = clk_enable(sc->clk); if (rv != 0) { device_printf(dev, "Cannot enable clock\n"); goto out; } freq = 0; rv = clk_get_freq(sc->clk, &freq); if (rv != 0) { device_printf(dev, "Cannot get clock frequency\n"); goto out; } rv = hwreset_deassert(sc->reset); if (rv != 0) { device_printf(dev, "Cannot clear reset: %d\n", rv); rv = ENXIO; goto out; } rv = phy_enable(sc->dev, sc->phy); if (rv != 0) { device_printf(dev, "Cannot enable phy: %d\n", rv); goto out; } /* Fill data for EHCI driver. */ esc->sc_vendor_get_port_speed = ehci_get_port_speed_hostc; esc->sc_vendor_post_reset = tegra_ehci_post_reset; esc->sc_io_tag = rman_get_bustag(sc->ehci_mem_res); esc->sc_bus.parent = dev; esc->sc_bus.devices = esc->sc_devices; esc->sc_bus.devices_max = EHCI_MAX_DEVICES; esc->sc_bus.dma_bits = 32; /* Allocate all DMA memory. */ rv = usb_bus_mem_alloc_all(&esc->sc_bus, USB_GET_DMA_TAG(dev), &ehci_iterate_hw_softc); sc->usb_alloc_called = 1; if (rv != 0) { device_printf(dev, "usb_bus_mem_alloc_all() failed\n"); rv = ENOMEM; goto out; } /* * Set handle to USB related registers subregion used by * generic EHCI driver. */ rv = bus_space_subregion(esc->sc_io_tag, rman_get_bushandle(sc->ehci_mem_res), TEGRA_EHCI_REG_OFF, TEGRA_EHCI_REG_SIZE, &esc->sc_io_hdl); if (rv != 0) { device_printf(dev, "Could not create USB memory subregion\n"); rv = ENXIO; goto out; } /* Setup interrupt handler. */ - rv = bus_setup_intr(dev, sc->ehci_irq_res, INTR_TYPE_BIO, NULL, - (driver_intr_t *)ehci_interrupt, esc, &esc->sc_intr_hdl); + rv = bus_setup_intr(dev, sc->ehci_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)ehci_interrupt, esc, &esc->sc_intr_hdl); if (rv != 0) { device_printf(dev, "Could not setup IRQ\n"); goto out; } /* Add USB bus device. */ esc->sc_bus.bdev = device_add_child(dev, "usbus", -1); if (esc->sc_bus.bdev == NULL) { device_printf(dev, "Could not add USB device\n"); goto out; } device_set_ivars(esc->sc_bus.bdev, &esc->sc_bus); esc->sc_id_vendor = USB_VENDOR_FREESCALE; strlcpy(esc->sc_vendor, "Nvidia", sizeof(esc->sc_vendor)); /* Set flags that affect ehci_init() behavior. */ esc->sc_flags |= EHCI_SCFLG_TT; esc->sc_flags |= EHCI_SCFLG_NORESTERM; rv = ehci_init(esc); if (rv != 0) { device_printf(dev, "USB init failed: %d\n", rv); goto out; } esc->sc_flags |= EHCI_SCFLG_DONEINIT; /* Probe the bus. */ rv = device_probe_and_attach(esc->sc_bus.bdev); if (rv != 0) { device_printf(dev, "device_probe_and_attach() failed\n"); goto out; } return (0); out: tegra_ehci_detach(dev); return (rv); } static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, tegra_ehci_probe), DEVMETHOD(device_attach, tegra_ehci_attach), DEVMETHOD(device_detach, tegra_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD_END }; static driver_t ehci_driver = { "ehci", ehci_methods, sizeof(struct tegra_ehci_softc) }; static devclass_t ehci_devclass; DRIVER_MODULE(ehci, simplebus, ehci_driver, ehci_devclass, 0, 0); MODULE_DEPEND(ehci, usb, 1, 1, 1); \ No newline at end of file Index: projects/release-pkg/sys/arm/ti/aintc.c =================================================================== --- projects/release-pkg/sys/arm/ti/aintc.c (revision 297604) +++ projects/release-pkg/sys/arm/ti/aintc.c (revision 297605) @@ -1,378 +1,378 @@ /*- * 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 ARM_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 INTS_SIR_ACTIVE_MASK 0x7f +#define INTC_SIR_ACTIVE_MASK 0x7f #define INTC_NIRQS 128 #ifdef ARM_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 ARM_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 ARM_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 &= INTS_SIR_ACTIVE_MASK; + 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 ti_aintc_softc *sc; if (data->type != INTR_MAP_DATA_FDT || data->fdt.ncells != 1 || data->fdt.cells[0] >= INTC_NIRQS) return (EINVAL); sc = device_get_softc(dev); *isrcp = &sc->aintc_isrcs[data->fdt.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 ARM_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 ARM_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 ARM_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: projects/release-pkg/sys/boot/fdt/dts/arm/bcm2835.dtsi =================================================================== --- projects/release-pkg/sys/boot/fdt/dts/arm/bcm2835.dtsi (revision 297604) +++ projects/release-pkg/sys/boot/fdt/dts/arm/bcm2835.dtsi (revision 297605) @@ -1,497 +1,497 @@ /* * Copyright (c) 2012 Oleksandr Tymoshenko * * 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$ */ / { #address-cells = <1>; #size-cells = <1>; cpus { cpu@0 { compatible = "arm,1176jzf-s"; }; }; SOC: axi { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <1>; reg = <0x20000000 0x01000000>; ranges = <0 0x20000000 0x01000000>; intc: interrupt-controller { compatible = "broadcom,bcm2835-armctrl-ic", "broadcom,bcm2708-armctrl-ic"; reg = <0xB200 0x200>; interrupt-controller; #interrupt-cells = <1>; /* Bank 0 * 0: ARM_TIMER * 1: ARM_MAILBOX * 2: ARM_DOORBELL_0 * 3: ARM_DOORBELL_1 * 4: VPU0_HALTED * 5: VPU1_HALTED * 6: ILLEGAL_TYPE0 * 7: ILLEGAL_TYPE1 */ /* Bank 1 * 0: TIMER0 16: DMA0 * 1: TIMER1 17: DMA1 * 2: TIMER2 18: VC_DMA2 * 3: TIMER3 19: VC_DMA3 * 4: CODEC0 20: DMA4 * 5: CODEC1 21: DMA5 * 6: CODEC2 22: DMA6 * 7: VC_JPEG 23: DMA7 * 8: ISP 24: DMA8 * 9: VC_USB 25: DMA9 * 10: VC_3D 26: DMA10 * 11: TRANSPOSER 27: DMA11 * 12: MULTICORESYNC0 28: DMA12 * 13: MULTICORESYNC1 29: AUX * 14: MULTICORESYNC2 30: ARM * 15: MULTICORESYNC3 31: VPUDMA */ /* Bank 2 * 0: HOSTPORT 16: SMI * 1: VIDEOSCALER 17: GPIO0 * 2: CCP2TX 18: GPIO1 * 3: SDC 19: GPIO2 * 4: DSI0 20: GPIO3 * 5: AVE 21: VC_I2C * 6: CAM0 22: VC_SPI * 7: CAM1 23: VC_I2SPCM * 8: HDMI0 24: VC_SDIO * 9: HDMI1 25: VC_UART * 10: PIXELVALVE1 26: SLIMBUS * 11: I2CSPISLV 27: VEC * 12: DSI1 28: CPG * 13: PWA0 29: RNG * 14: PWA1 30: VC_ARASANSDIO * 15: CPR 31: AVSPMON */ }; timer { compatible = "broadcom,bcm2835-system-timer", "broadcom,bcm2708-system-timer"; reg = <0x3000 0x1000>; interrupts = <8 9 10 11>; interrupt-parent = <&intc>; clock-frequency = <1000000>; }; armtimer { /* Not AMBA compatible */ compatible = "broadcom,bcm2835-sp804", "arm,sp804"; reg = <0xB400 0x24>; interrupts = <0>; interrupt-parent = <&intc>; }; watchdog0 { compatible = "broadcom,bcm2835-wdt", "broadcom,bcm2708-wdt"; reg = <0x10001c 0x0c>; /* 0x1c, 0x20, 0x24 */ }; gpio: gpio { compatible = "broadcom,bcm2835-gpio", "broadcom,bcm2708-gpio"; reg = <0x200000 0xb0>; /* Unusual arrangement of interrupts * (determined by testing) * 17: Bank 0 (GPIOs 0-31) * 19: Bank 1 (GPIOs 32-53) * 18: Bank 2 * 20: All banks (GPIOs 0-53) */ interrupts = <57 59 58 60>; interrupt-parent = <&intc>; gpio-controller; #gpio-cells = <2>; interrupt-controller; - #interrupt-cells = <1>; + #interrupt-cells = <2>; pinctrl-names = "default"; pinctrl-0 = <&pins_reserved>; /* Pins that can short 3.3V to GND in output mode: 46-47 * Pins used by VideoCore: 48-53 */ broadcom,read-only = <46>, <47>, <48>, <49>, <50>, <51>, <52>, <53>; /* BSC0 */ pins_bsc0_a: bsc0_a { broadcom,pins = <0>, <1>; }; pins_bsc0_b: bsc0_b { broadcom,pins = <28>, <29>; }; pins_bsc0_c: bsc0_c { broadcom,pins = <44>, <45>; }; /* BSC1 */ pins_bsc1_a: bsc1_a { broadcom,pins = <2>, <3>; }; pins_bsc1_b: bsc1_b { broadcom,pins = <44>, <45>; }; /* GPCLK0 */ pins_gpclk0_a: gpclk0_a { broadcom,pins = <4>; }; pins_gpclk0_b: gpclk0_b { broadcom,pins = <20>; }; pins_gpclk0_c: gpclk0_c { broadcom,pins = <32>; }; pins_gpclk0_d: gpclk0_d { broadcom,pins = <34>; }; /* GPCLK1 */ pins_gpclk1_a: gpclk1_a { broadcom,pins = <5>; }; pins_gpclk1_b: gpclk1_b { broadcom,pins = <21>; }; pins_gpclk1_c: gpclk1_c { broadcom,pins = <42>; }; pins_gpclk1_d: gpclk1_d { broadcom,pins = <44>; }; /* GPCLK2 */ pins_gpclk2_a: gpclk2_a { broadcom,pins = <6>; }; pins_gpclk2_b: gpclk2_b { broadcom,pins = <43>; }; /* SPI0 */ pins_spi0_a: spi0_a { broadcom,pins = <7>, <8>, <9>, <10>, <11>; }; pins_spi0_b: spi0_b { broadcom,pins = <35>, <36>, <37>, <38>, <39>; }; /* PWM */ pins_pwm0_a: pwm0_a { broadcom,pins = <12>; }; pins_pwm0_b: pwm0_b { broadcom,pins = <18>; }; pins_pwm0_c: pwm0_c { broadcom,pins = <40>; }; pins_pwm1_a: pwm1_a { broadcom,pins = <13>; }; pins_pwm1_b: pwm1_b { broadcom,pins = <19>; }; pins_pwm1_c: pwm1_c { broadcom,pins = <41>; }; pins_pwm1_d: pwm1_d { broadcom,pins = <45>; }; /* UART0 */ pins_uart0_a: uart0_a { broadcom,pins = <14>, <15>; }; pins_uart0_b: uart0_b { broadcom,pins = <32>, <33>; }; pins_uart0_c: uart0_c { broadcom,pins = <36>, <37>; }; pins_uart0_fc_a: uart0_fc_a { broadcom,pins = <16>, <17>; }; pins_uart0_fc_b: uart0_fc_b { broadcom,pins = <30>, <31>; }; pins_uart0_fc_c: uart0_fc_c { broadcom,pins = <39>, <38>; }; /* PCM */ pins_pcm_a: pcm_a { broadcom,pins = <18>, <19>, <20>, <21>; }; pins_pcm_b: pcm_b { broadcom,pins = <28>, <29>, <30>, <31>; }; /* Secondary Address Bus */ pins_sm_addr_a: sm_addr_a { broadcom,pins = <5>, <4>, <3>, <2>, <1>, <0>; }; pins_sm_addr_b: sm_addr_b { broadcom,pins = <33>, <32>, <31>, <30>, <29>, <28>; }; pins_sm_ctl_a: sm_ctl_a { broadcom,pins = <6>, <7>; }; pins_sm_ctl_b: sm_ctl_b { broadcom,pins = <34>, <35>; }; pins_sm_data_8bit_a: sm_data_8bit_a { broadcom,pins = <8>, <9>, <10>, <11>, <12>, <13>, <14>, <15>; }; pins_sm_data_8bit_b: sm_data_8bit_b { broadcom,pins = <36>, <37>, <38>, <39>, <40>, <41>, <42>, <43>; }; pins_sm_data_16bit: sm_data_16bit { broadcom,pins = <16>, <17>, <18>, <19>, <20>, <21>, <22>, <23>; }; pins_sm_data_18bit: sm_data_18bit { broadcom,pins = <24>, <25>; }; /* BSCSL */ pins_bscsl: bscsl { broadcom,pins = <18>, <19>; }; /* SPISL */ pins_spisl: spisl { broadcom,pins = <18>, <19>, <20>, <21>; }; /* SPI1 */ pins_spi1: spi1 { broadcom,pins = <16>, <17>, <18>, <19>, <20>, <21>; }; /* UART1 */ pins_uart1_a: uart1_a { broadcom,pins = <14>, <15>; }; pins_uart1_b: uart1_b { broadcom,pins = <32>, <33>; }; pins_uart1_c: uart1_c { broadcom,pins = <40>, <41>; }; pins_uart1_fc_a: uart1_fc_a { broadcom,pins = <16>, <17>; }; pins_uart1_fc_b: uart1_fc_b { broadcom,pins = <30>, <31>; }; pins_uart1_fc_c: uart1_fc_c { broadcom,pins = <43>, <42>; }; /* SPI2 */ pins_spi2: spi2 { broadcom,pins = <40>, <41>, <42>, <43>, <44>, <45>; }; /* ARM JTAG */ pins_arm_jtag_trst: arm_jtag_trst { broadcom,pins = <22>; }; pins_arm_jtag_a: arm_jtag_a { broadcom,pins = <4>, <5>, <6>, <12>, <13>; }; pins_arm_jtag_b: arm_jtag_b { broadcom,pins = <23>, <24>, <25>, <26>, <27>; }; /* Reserved */ pins_reserved: reserved { broadcom,pins = <48>, <49>, <50>, <51>, <52>, <53>; }; }; bsc0 { #address-cells = <1>; #size-cells = <0>; compatible = "broadcom,bcm2835-bsc", "broadcom,bcm2708-bsc"; reg = <0x205000 0x20>; interrupts = <61>; interrupt-parent = <&intc>; }; bsc1 { #address-cells = <1>; #size-cells = <0>; compatible = "broadcom,bcm2835-bsc", "broadcom,bcm2708-bsc"; reg = <0x804000 0x20>; interrupts = <61>; interrupt-parent = <&intc>; }; spi0 { compatible = "broadcom,bcm2835-spi", "broadcom,bcm2708-spi"; reg = <0x204000 0x20>; interrupts = <62>; interrupt-parent = <&intc>; }; dma: dma { compatible = "broadcom,bcm2835-dma", "broadcom,bcm2708-dma"; reg = <0x7000 0x1000>, <0xE05000 0x1000>; interrupts = <24 25 26 27 28 29 30 31 32 33 34 35 36>; interrupt-parent = <&intc>; broadcom,channels = <0>; /* Set by VideoCore */ }; vc_mbox: mbox { compatible = "broadcom,bcm2835-mbox", "broadcom,bcm2708-mbox"; reg = <0xB880 0x40>; interrupts = <1>; interrupt-parent = <&intc>; /* Channels * 0: Power * 1: Frame buffer * 2: Virtual UART * 3: VCHIQ * 4: LEDs * 5: Buttons * 6: Touch screen */ }; sdhci { compatible = "broadcom,bcm2835-sdhci", "broadcom,bcm2708-sdhci"; reg = <0x300000 0x100>; interrupts = <70>; interrupt-parent = <&intc>; clock-frequency = <50000000>; /* Set by VideoCore */ }; uart0: uart0 { compatible = "broadcom,bcm2835-uart", "broadcom,bcm2708-uart", "arm,pl011", "arm,primecell"; reg = <0x201000 0x1000>; interrupts = <65>; interrupt-parent = <&intc>; clock-frequency = <3000000>; /* Set by VideoCore */ reg-shift = <2>; }; vchiq: vchiq { compatible = "broadcom,bcm2835-vchiq"; reg = <0xB800 0x50>; interrupts = <2>; interrupt-parent = <&intc>; cache-line-size = <32>; }; usb { compatible = "broadcom,bcm2835-usb", "broadcom,bcm2708-usb", "synopsys,designware-hs-otg2"; reg = <0x980000 0x20000>; interrupts = <17>; interrupt-parent = <&intc>; #address-cells = <1>; #size-cells = <0>; }; }; }; Index: projects/release-pkg/sys/boot/fdt/dts/arm/bcm2836.dtsi =================================================================== --- projects/release-pkg/sys/boot/fdt/dts/arm/bcm2836.dtsi (revision 297604) +++ projects/release-pkg/sys/boot/fdt/dts/arm/bcm2836.dtsi (revision 297605) @@ -1,490 +1,490 @@ /* * Copyright (c) 2012 Oleksandr Tymoshenko * * 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$ */ / { #address-cells = <1>; #size-cells = <1>; timer { compatible = "arm,armv7-timer"; clock-frequency = <19200000>; interrupts = <0 1 3 2>; interrupt-parent = <&local_intc>; }; SOC: axi { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <1>; reg = <0x3f000000 0x01000000>; ranges = <0 0x3f000000 0x01000000>, <0x40000000 0x40000000 0x00001000>; local_intc: local_intc { compatible = "brcm,bcm2836-l1-intc"; reg = <0x40000000 0x100>; interrupt-controller; #interrupt-cells = <1>; interrupt-parent = <&local_intc>; }; intc: interrupt-controller { compatible = "broadcom,bcm2835-armctrl-ic", "broadcom,bcm2708-armctrl-ic"; reg = <0xB200 0x200>; interrupt-parent = <&local_intc>; interrupts = <8>; interrupt-controller; #interrupt-cells = <1>; /* Bank 0 * 0: ARM_TIMER * 1: ARM_MAILBOX * 2: ARM_DOORBELL_0 * 3: ARM_DOORBELL_1 * 4: VPU0_HALTED * 5: VPU1_HALTED * 6: ILLEGAL_TYPE0 * 7: ILLEGAL_TYPE1 */ /* Bank 1 * 0: TIMER0 16: DMA0 * 1: TIMER1 17: DMA1 * 2: TIMER2 18: VC_DMA2 * 3: TIMER3 19: VC_DMA3 * 4: CODEC0 20: DMA4 * 5: CODEC1 21: DMA5 * 6: CODEC2 22: DMA6 * 7: VC_JPEG 23: DMA7 * 8: ISP 24: DMA8 * 9: VC_USB 25: DMA9 * 10: VC_3D 26: DMA10 * 11: TRANSPOSER 27: DMA11 * 12: MULTICORESYNC0 28: DMA12 * 13: MULTICORESYNC1 29: AUX * 14: MULTICORESYNC2 30: ARM * 15: MULTICORESYNC3 31: VPUDMA */ /* Bank 2 * 0: HOSTPORT 16: SMI * 1: VIDEOSCALER 17: GPIO0 * 2: CCP2TX 18: GPIO1 * 3: SDC 19: GPIO2 * 4: DSI0 20: GPIO3 * 5: AVE 21: VC_I2C * 6: CAM0 22: VC_SPI * 7: CAM1 23: VC_I2SPCM * 8: HDMI0 24: VC_SDIO * 9: HDMI1 25: VC_UART * 10: PIXELVALVE1 26: SLIMBUS * 11: I2CSPISLV 27: VEC * 12: DSI1 28: CPG * 13: PWA0 29: RNG * 14: PWA1 30: VC_ARASANSDIO * 15: CPR 31: AVSPMON */ }; watchdog0 { compatible = "broadcom,bcm2835-wdt", "broadcom,bcm2708-wdt"; reg = <0x10001c 0x0c>; /* 0x1c, 0x20, 0x24 */ }; gpio: gpio { compatible = "broadcom,bcm2835-gpio", "broadcom,bcm2708-gpio"; reg = <0x200000 0xb0>; /* Unusual arrangement of interrupts * (determined by testing) * 17: Bank 0 (GPIOs 0-31) * 19: Bank 1 (GPIOs 32-53) * 18: Bank 2 * 20: All banks (GPIOs 0-53) */ interrupts = <57 59 58 60>; interrupt-parent = <&intc>; gpio-controller; #gpio-cells = <2>; interrupt-controller; - #interrupt-cells = <1>; + #interrupt-cells = <2>; pinctrl-names = "default"; pinctrl-0 = <&pins_reserved>; /* Pins that can short 3.3V to GND in output mode: 46 * Pins used by VideoCore: 48-53 */ broadcom,read-only = <46>, <48>, <49>, <50>, <51>, <52>, <53>; /* BSC0 */ pins_bsc0_a: bsc0_a { broadcom,pins = <0>, <1>; }; pins_bsc0_b: bsc0_b { broadcom,pins = <28>, <29>; }; pins_bsc0_c: bsc0_c { broadcom,pins = <44>, <45>; }; /* BSC1 */ pins_bsc1_a: bsc1_a { broadcom,pins = <2>, <3>; }; pins_bsc1_b: bsc1_b { broadcom,pins = <44>, <45>; }; /* GPCLK0 */ pins_gpclk0_a: gpclk0_a { broadcom,pins = <4>; }; pins_gpclk0_b: gpclk0_b { broadcom,pins = <20>; }; pins_gpclk0_c: gpclk0_c { broadcom,pins = <32>; }; pins_gpclk0_d: gpclk0_d { broadcom,pins = <34>; }; /* GPCLK1 */ pins_gpclk1_a: gpclk1_a { broadcom,pins = <5>; }; pins_gpclk1_b: gpclk1_b { broadcom,pins = <21>; }; pins_gpclk1_c: gpclk1_c { broadcom,pins = <42>; }; pins_gpclk1_d: gpclk1_d { broadcom,pins = <44>; }; /* GPCLK2 */ pins_gpclk2_a: gpclk2_a { broadcom,pins = <6>; }; pins_gpclk2_b: gpclk2_b { broadcom,pins = <43>; }; /* SPI0 */ pins_spi0_a: spi0_a { broadcom,pins = <7>, <8>, <9>, <10>, <11>; }; pins_spi0_b: spi0_b { broadcom,pins = <35>, <36>, <37>, <38>, <39>; }; /* PWM */ pins_pwm0_a: pwm0_a { broadcom,pins = <12>; }; pins_pwm0_b: pwm0_b { broadcom,pins = <18>; }; pins_pwm0_c: pwm0_c { broadcom,pins = <40>; }; pins_pwm1_a: pwm1_a { broadcom,pins = <13>; }; pins_pwm1_b: pwm1_b { broadcom,pins = <19>; }; pins_pwm1_c: pwm1_c { broadcom,pins = <41>; }; pins_pwm1_d: pwm1_d { broadcom,pins = <45>; }; /* UART0 */ pins_uart0_a: uart0_a { broadcom,pins = <14>, <15>; }; pins_uart0_b: uart0_b { broadcom,pins = <32>, <33>; }; pins_uart0_c: uart0_c { broadcom,pins = <36>, <37>; }; pins_uart0_fc_a: uart0_fc_a { broadcom,pins = <16>, <17>; }; pins_uart0_fc_b: uart0_fc_b { broadcom,pins = <30>, <31>; }; pins_uart0_fc_c: uart0_fc_c { broadcom,pins = <39>, <38>; }; /* PCM */ pins_pcm_a: pcm_a { broadcom,pins = <18>, <19>, <20>, <21>; }; pins_pcm_b: pcm_b { broadcom,pins = <28>, <29>, <30>, <31>; }; /* Secondary Address Bus */ pins_sm_addr_a: sm_addr_a { broadcom,pins = <5>, <4>, <3>, <2>, <1>, <0>; }; pins_sm_addr_b: sm_addr_b { broadcom,pins = <33>, <32>, <31>, <30>, <29>, <28>; }; pins_sm_ctl_a: sm_ctl_a { broadcom,pins = <6>, <7>; }; pins_sm_ctl_b: sm_ctl_b { broadcom,pins = <34>, <35>; }; pins_sm_data_8bit_a: sm_data_8bit_a { broadcom,pins = <8>, <9>, <10>, <11>, <12>, <13>, <14>, <15>; }; pins_sm_data_8bit_b: sm_data_8bit_b { broadcom,pins = <36>, <37>, <38>, <39>, <40>, <41>, <42>, <43>; }; pins_sm_data_16bit: sm_data_16bit { broadcom,pins = <16>, <17>, <18>, <19>, <20>, <21>, <22>, <23>; }; pins_sm_data_18bit: sm_data_18bit { broadcom,pins = <24>, <25>; }; /* BSCSL */ pins_bscsl: bscsl { broadcom,pins = <18>, <19>; }; /* SPISL */ pins_spisl: spisl { broadcom,pins = <18>, <19>, <20>, <21>; }; /* SPI1 */ pins_spi1: spi1 { broadcom,pins = <16>, <17>, <18>, <19>, <20>, <21>; }; /* UART1 */ pins_uart1_a: uart1_a { broadcom,pins = <14>, <15>; }; pins_uart1_b: uart1_b { broadcom,pins = <32>, <33>; }; pins_uart1_c: uart1_c { broadcom,pins = <40>, <41>; }; pins_uart1_fc_a: uart1_fc_a { broadcom,pins = <16>, <17>; }; pins_uart1_fc_b: uart1_fc_b { broadcom,pins = <30>, <31>; }; pins_uart1_fc_c: uart1_fc_c { broadcom,pins = <43>, <42>; }; /* SPI2 */ pins_spi2: spi2 { broadcom,pins = <40>, <41>, <42>, <43>, <44>, <45>; }; /* ARM JTAG */ pins_arm_jtag_trst: arm_jtag_trst { broadcom,pins = <22>; }; pins_arm_jtag_a: arm_jtag_a { broadcom,pins = <4>, <5>, <6>, <12>, <13>; }; pins_arm_jtag_b: arm_jtag_b { broadcom,pins = <23>, <24>, <25>, <26>, <27>; }; /* Reserved */ pins_reserved: reserved { broadcom,pins = <48>, <49>, <50>, <51>, <52>, <53>; }; }; bsc0 { #address-cells = <1>; #size-cells = <0>; compatible = "broadcom,bcm2835-bsc", "broadcom,bcm2708-bsc"; reg = <0x205000 0x20>; interrupts = <61>; interrupt-parent = <&intc>; }; bsc1 { #address-cells = <1>; #size-cells = <0>; compatible = "broadcom,bcm2835-bsc", "broadcom,bcm2708-bsc"; reg = <0x804000 0x20>; interrupts = <61>; interrupt-parent = <&intc>; }; spi0 { compatible = "broadcom,bcm2835-spi", "broadcom,bcm2708-spi"; reg = <0x204000 0x20>; interrupts = <62>; interrupt-parent = <&intc>; }; dma: dma { compatible = "broadcom,bcm2835-dma", "broadcom,bcm2708-dma"; reg = <0x7000 0x1000>, <0xE05000 0x1000>; interrupts = <24 25 26 27 28 29 30 31 32 33 34 35 36>; interrupt-parent = <&intc>; broadcom,channels = <0>; /* Set by VideoCore */ }; vc_mbox: mbox { compatible = "broadcom,bcm2835-mbox", "broadcom,bcm2708-mbox"; reg = <0xB880 0x40>; interrupts = <1>; interrupt-parent = <&intc>; /* Channels * 0: Power * 1: Frame buffer * 2: Virtual UART * 3: VCHIQ * 4: LEDs * 5: Buttons * 6: Touch screen */ }; sdhci { compatible = "broadcom,bcm2835-sdhci", "broadcom,bcm2708-sdhci"; reg = <0x300000 0x100>; interrupts = <70>; interrupt-parent = <&intc>; clock-frequency = <250000000>; /* Set by VideoCore */ }; uart0: uart0 { compatible = "broadcom,bcm2835-uart", "broadcom,bcm2708-uart", "arm,pl011", "arm,primecell"; reg = <0x201000 0x1000>; interrupts = <65>; interrupt-parent = <&intc>; clock-frequency = <3000000>; /* Set by VideoCore */ reg-shift = <2>; }; vchiq: vchiq { compatible = "broadcom,bcm2835-vchiq"; reg = <0xB800 0x50>; interrupts = <2>; interrupt-parent = <&intc>; cache-line-size = <32>; }; usb { compatible = "broadcom,bcm2835-usb", "broadcom,bcm2708-usb", "synopsys,designware-hs-otg2"; reg = <0x980000 0x20000>; interrupts = <17>; interrupt-parent = <&intc>; #address-cells = <1>; #size-cells = <0>; }; }; }; Index: projects/release-pkg/sys/boot =================================================================== --- projects/release-pkg/sys/boot (revision 297604) +++ projects/release-pkg/sys/boot (revision 297605) Property changes on: projects/release-pkg/sys/boot ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/sys/boot:r297567-297604 Index: projects/release-pkg/sys/dev/fdc/fdc.c =================================================================== --- projects/release-pkg/sys/dev/fdc/fdc.c (revision 297604) +++ projects/release-pkg/sys/dev/fdc/fdc.c (revision 297605) @@ -1,2335 +1,2335 @@ /*- * Copyright (c) 2004 Poul-Henning Kamp * Copyright (c) 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Don Ahn. * * Libretto PCMCIA floppy support by David Horwitt (dhorwitt@ucsd.edu) * aided by the Linux floppy driver modifications from David Bateman * (dbateman@eng.uts.edu.au). * * Copyright (c) 1993, 1994 by * jc@irbs.UUCP (John Capo) * vak@zebub.msk.su (Serge Vakulenko) * ache@astral.msk.su (Andrew A. Chernov) * * Copyright (c) 1993, 1994, 1995 by * joerg_wunsch@uriah.sax.de (Joerg Wunsch) * dufault@hda.com (Peter Dufault) * * Copyright (c) 2001 Joerg Wunsch, * joerg_wunsch@uriah.heep.sax.de (Joerg Wunsch) * * 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: @(#)fd.c 7.4 (Berkeley) 5/25/91 * */ #include __FBSDID("$FreeBSD$"); #include "opt_fdc.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 #ifdef PC98 #include #else #include #include #endif #include #include /* * Runtime configuration hints/flags */ /* configuration flags for fd */ #define FD_TYPEMASK 0x0f /* drive type, matches enum * fd_drivetype; on i386 machines, if * given as 0, use RTC type for fd0 * and fd1 */ #define FD_NO_CHLINE 0x10 /* drive does not support changeline * aka. unit attention */ #define FD_NO_PROBE 0x20 /* don't probe drive (seek test), just * assume it is there */ /* * Things that could conceiveably considered parameters or tweakables */ /* * Maximal number of bytes in a cylinder. * This is used for ISADMA bouncebuffer allocation and sets the max * xfersize we support. * * 2.88M format has 2 x 36 x 512, allow for hacked up density. */ #define MAX_BYTES_PER_CYL (2 * 40 * 512) /* * Timeout value for the PIO loops to wait until the FDC main status * register matches our expectations (request for master, direction * bit). This is supposed to be a number of microseconds, although * timing might actually not be very accurate. * * Timeouts of 100 msec are believed to be required for some broken * (old) hardware. */ #define FDSTS_TIMEOUT 100000 /* * After this many errors, stop whining. Close will reset this count. */ #define FDC_ERRMAX 100 /* * AutoDensity search lists for each drive type. */ static struct fd_type fd_searchlist_360k[] = { #ifndef PC98 { FDF_5_360 }, #endif { 0 } }; static struct fd_type fd_searchlist_12m[] = { #ifdef PC98 { FDF_5_1200 | FL_AUTO }, { FDF_5_720 | FL_AUTO }, { FDF_5_360 | FL_AUTO }, { FDF_5_640 | FL_AUTO }, { FDF_5_1230 | FL_AUTO }, #else { FDF_5_1200 | FL_AUTO }, { FDF_5_400 | FL_AUTO }, { FDF_5_360 | FL_2STEP | FL_AUTO}, #endif { 0 } }; static struct fd_type fd_searchlist_720k[] = { #ifndef PC98 { FDF_3_720 }, #endif { 0 } }; static struct fd_type fd_searchlist_144m[] = { #ifdef PC98 { FDF_3_1440 | FL_AUTO}, { FDF_3_1200 | FL_AUTO}, { FDF_3_720 | FL_AUTO}, { FDF_3_360 | FL_AUTO}, { FDF_3_640 | FL_AUTO}, { FDF_3_1230 | FL_AUTO}, #else { FDF_3_1440 | FL_AUTO}, { FDF_3_720 | FL_AUTO}, #endif { 0 } }; static struct fd_type fd_searchlist_288m[] = { #ifndef PC98 { FDF_3_1440 | FL_AUTO }, #if 0 { FDF_3_2880 | FL_AUTO }, /* XXX: probably doesn't work */ #endif { FDF_3_720 | FL_AUTO}, #endif { 0 } }; /* * Order must match enum fd_drivetype in . */ static struct fd_type *fd_native_types[] = { NULL, /* FDT_NONE */ fd_searchlist_360k, /* FDT_360K */ fd_searchlist_12m, /* FDT_12M */ fd_searchlist_720k, /* FDT_720K */ fd_searchlist_144m, /* FDT_144M */ fd_searchlist_288m, /* FDT_288M_1 (mapped to FDT_288M) */ fd_searchlist_288m, /* FDT_288M */ }; /* * Internals start here */ #ifdef PC98 /* registers */ #define FDSTS 0 /* NEC 765 Main Status Register (R) */ #define FDDATA 1 /* NEC 765 Data Register (R/W) */ #define FDCTL 2 /* FD Control Register */ #define FDC_RST 0x80 /* FDC RESET */ #define FDC_RDY 0x40 /* force READY */ #define FDC_DD 0x20 /* FDD Mode Exchange 0:1M 1:640K */ #define FDC_DMAE 0x10 /* enable floppy DMA */ #define FDC_MTON 0x08 /* MOTOR ON (when EMTON=1)*/ #define FDC_TMSK 0x04 /* TIMER MASK */ #define FDC_TTRG 0x01 /* TIMER TRIGER */ #define FDP 3 #define FDP_EMTON 0x04 /* enable MTON */ #define FDP_FDDEXC 0x02 /* FDD Mode Exchange 1:1M 0:640K */ #define FDP_PORTEXC 0x01 /* PORT Exchane 1:1M 0:640K */ #define FDEM 4 #else /* registers */ #define FDOUT 2 /* Digital Output Register (W) */ #define FDO_FDSEL 0x03 /* floppy device select */ #define FDO_FRST 0x04 /* floppy controller reset */ #define FDO_FDMAEN 0x08 /* enable floppy DMA and Interrupt */ #define FDO_MOEN0 0x10 /* motor enable drive 0 */ #define FDO_MOEN1 0x20 /* motor enable drive 1 */ #define FDO_MOEN2 0x40 /* motor enable drive 2 */ #define FDO_MOEN3 0x80 /* motor enable drive 3 */ #define FDSTS 4 /* NEC 765 Main Status Register (R) */ #define FDDSR 4 /* Data Rate Select Register (W) */ #define FDDATA 5 /* NEC 765 Data Register (R/W) */ #define FDCTL 7 /* Control Register (W) */ #endif /* PC98 */ /* * The YE-DATA PC Card floppies use PIO to read in the data rather * than DMA due to the wild variability of DMA for the PC Card * devices. DMA was deleted from the PC Card specification in version * 7.2 of the standard, but that post-dates the YE-DATA devices by many * years. * * In addition, if we cannot setup the DMA resources for the ISA * attachment, we'll use this same offset for data transfer. However, * that almost certainly won't work. * * For this mode, offset 0 and 1 must be used to setup the transfer * for this floppy. This is OK for PC Card YE Data devices, but for * ISA this is likely wrong. These registers are only available on * those systems that map them to the floppy drive. Newer systems do * not do this, and we should likely prohibit access to them (or * disallow NODMA to be set). */ #define FDBCDR 0 /* And 1 */ #define FD_YE_DATAPORT 6 /* Drive Data port */ #ifndef PC98 #define FDI_DCHG 0x80 /* diskette has been changed */ /* requires drive and motor being selected */ /* is cleared by any step pulse to drive */ #endif /* * We have three private BIO commands. */ #define BIO_PROBE BIO_CMD0 #define BIO_RDID BIO_CMD1 #define BIO_FMT BIO_CMD2 /* * Per drive structure (softc). */ struct fd_data { u_char *fd_ioptr; /* IO pointer */ u_int fd_iosize; /* Size of IO chunks */ u_int fd_iocount; /* Outstanding requests */ struct fdc_data *fdc; /* pointer to controller structure */ int fdsu; /* this units number on this controller */ enum fd_drivetype type; /* drive type */ struct fd_type *ft; /* pointer to current type descriptor */ struct fd_type fts; /* type descriptors */ int sectorsize; int flags; #define FD_WP (1<<0) /* Write protected */ #define FD_MOTOR (1<<1) /* motor should be on */ #define FD_MOTORWAIT (1<<2) /* motor should be on */ #define FD_EMPTY (1<<3) /* no media */ #define FD_NEWDISK (1<<4) /* media changed */ #define FD_ISADMA (1<<5) /* isa dma started */ int track; /* where we think the head is */ #define FD_NO_TRACK -2 int options; /* FDOPT_* */ struct callout toffhandle; struct g_geom *fd_geom; struct g_provider *fd_provider; device_t dev; struct bio_queue_head fd_bq; #ifdef PC98 int pc98_trans; #endif }; #define FD_NOT_VALID -2 static driver_intr_t fdc_intr; static driver_filter_t fdc_intr_fast; static void fdc_reset(struct fdc_data *); static int fd_probe_disk(struct fd_data *, int *); static SYSCTL_NODE(_debug, OID_AUTO, fdc, CTLFLAG_RW, 0, "fdc driver"); static int fifo_threshold = 8; SYSCTL_INT(_debug_fdc, OID_AUTO, fifo, CTLFLAG_RW, &fifo_threshold, 0, "FIFO threshold setting"); static int debugflags = 0; SYSCTL_INT(_debug_fdc, OID_AUTO, debugflags, CTLFLAG_RW, &debugflags, 0, "Debug flags"); static int retries = 10; SYSCTL_INT(_debug_fdc, OID_AUTO, retries, CTLFLAG_RW, &retries, 0, "Number of retries to attempt"); #ifdef PC98 static int spec1 = NE7_SPEC_1(4, 240); #else static int spec1 = NE7_SPEC_1(6, 240); #endif SYSCTL_INT(_debug_fdc, OID_AUTO, spec1, CTLFLAG_RW, &spec1, 0, "Specification byte one (step-rate + head unload)"); #ifdef PC98 static int spec2 = NE7_SPEC_2(2, 0); #else static int spec2 = NE7_SPEC_2(16, 0); #endif SYSCTL_INT(_debug_fdc, OID_AUTO, spec2, CTLFLAG_RW, &spec2, 0, "Specification byte two (head load time + no-dma)"); static int settle; SYSCTL_INT(_debug_fdc, OID_AUTO, settle, CTLFLAG_RW, &settle, 0, "Head settling time in sec/hz"); static void fdprinttype(struct fd_type *ft) { printf("(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,0x%x)", ft->sectrac, ft->secsize, ft->datalen, ft->gap, ft->tracks, ft->size, ft->trans, ft->heads, ft->f_gap, ft->f_inter, ft->offset_side2, ft->flags); } static void fdsettype(struct fd_data *fd, struct fd_type *ft) { fd->ft = ft; ft->size = ft->sectrac * ft->heads * ft->tracks; fd->sectorsize = 128 << fd->ft->secsize; } /* * Bus space handling (access to low-level IO). */ static inline void fdregwr(struct fdc_data *fdc, int reg, uint8_t v) { bus_space_write_1(fdc->iot, fdc->ioh[reg], fdc->ioff[reg], v); } static inline uint8_t fdregrd(struct fdc_data *fdc, int reg) { return bus_space_read_1(fdc->iot, fdc->ioh[reg], fdc->ioff[reg]); } static void fdctl_wr(struct fdc_data *fdc, u_int8_t v) { fdregwr(fdc, FDCTL, v); } #ifndef PC98 static void fdout_wr(struct fdc_data *fdc, u_int8_t v) { fdregwr(fdc, FDOUT, v); } #endif static u_int8_t fdsts_rd(struct fdc_data *fdc) { return fdregrd(fdc, FDSTS); } #ifndef PC98 static void fddsr_wr(struct fdc_data *fdc, u_int8_t v) { fdregwr(fdc, FDDSR, v); } #endif static void fddata_wr(struct fdc_data *fdc, u_int8_t v) { fdregwr(fdc, FDDATA, v); } static u_int8_t fddata_rd(struct fdc_data *fdc) { return fdregrd(fdc, FDDATA); } #ifndef PC98 static u_int8_t fdin_rd(struct fdc_data *fdc) { return fdregrd(fdc, FDCTL); } #endif /* * Magic pseudo-DMA initialization for YE FDC. Sets count and * direction. */ static void fdbcdr_wr(struct fdc_data *fdc, int iswrite, uint16_t count) { fdregwr(fdc, FDBCDR, (count - 1) & 0xff); fdregwr(fdc, FDBCDR + 1, (iswrite ? 0x80 : 0) | (((count - 1) >> 8) & 0x7f)); } static int fdc_err(struct fdc_data *fdc, const char *s) { fdc->fdc_errs++; if (s) { if (fdc->fdc_errs < FDC_ERRMAX) device_printf(fdc->fdc_dev, "%s", s); else if (fdc->fdc_errs == FDC_ERRMAX) device_printf(fdc->fdc_dev, "too many errors, not " "logging any more\n"); } return (1); } /* * FDC IO functions, take care of the main status register, timeout * in case the desired status bits are never set. * * These PIO loops initially start out with short delays between * each iteration in the expectation that the required condition * is usually met quickly, so it can be handled immediately. */ static int fdc_in(struct fdc_data *fdc, int *ptr) { int i, j, step; step = 1; for (j = 0; j < FDSTS_TIMEOUT; j += step) { i = fdsts_rd(fdc) & (NE7_DIO | NE7_RQM); if (i == (NE7_DIO|NE7_RQM)) { i = fddata_rd(fdc); if (ptr) *ptr = i; return (0); } if (i == NE7_RQM) return (fdc_err(fdc, "ready for output in input\n")); step += step; DELAY(step); } return (fdc_err(fdc, bootverbose? "input ready timeout\n": 0)); } static int fdc_out(struct fdc_data *fdc, int x) { int i, j, step; step = 1; for (j = 0; j < FDSTS_TIMEOUT; j += step) { i = fdsts_rd(fdc) & (NE7_DIO | NE7_RQM); if (i == NE7_RQM) { fddata_wr(fdc, x); return (0); } if (i == (NE7_DIO|NE7_RQM)) return (fdc_err(fdc, "ready for input in output\n")); step += step; DELAY(step); } return (fdc_err(fdc, bootverbose? "output ready timeout\n": 0)); } /* * fdc_cmd: Send a command to the chip. * Takes a varargs with this structure: * # of output bytes * output bytes as int [...] * # of input bytes * input bytes as int* [...] */ static int fdc_cmd(struct fdc_data *fdc, int n_out, ...) { u_char cmd = 0; int n_in; int n, i; va_list ap; va_start(ap, n_out); for (n = 0; n < n_out; n++) { i = va_arg(ap, int); if (n == 0) cmd = i; if (fdc_out(fdc, i) < 0) { char msg[50]; snprintf(msg, sizeof(msg), "cmd %x failed at out byte %d of %d\n", cmd, n + 1, n_out); fdc->flags |= FDC_NEEDS_RESET; va_end(ap); return fdc_err(fdc, msg); } } n_in = va_arg(ap, int); for (n = 0; n < n_in; n++) { int *ptr = va_arg(ap, int *); if (fdc_in(fdc, ptr) < 0) { char msg[50]; snprintf(msg, sizeof(msg), "cmd %02x failed at in byte %d of %d\n", cmd, n + 1, n_in); fdc->flags |= FDC_NEEDS_RESET; va_end(ap); return fdc_err(fdc, msg); } } va_end(ap); return (0); } #ifdef PC98 static void fd_motor(struct fd_data *fd, int turnon); static int pc98_trans = 0; /* 0 : HD , 1 : DD , 2 : 1.44 */ static int pc98_trans_prev = -1; static void set_density(struct fdc_data *fdc) { /* always motor on */ fdregwr(fdc, FDP, (pc98_trans != 1 ? FDP_FDDEXC : 0) | FDP_PORTEXC); DELAY(100); fdctl_wr(fdc, FDC_RST | FDC_DMAE); /* in the case of note W, always inhibit 100ms timer */ } static int pc98_fd_check_ready(struct fd_data *fd) { struct fdc_data *fdc = fd->fdc; int retry = 0, status; int fdu = device_get_unit(fd->dev); while (retry++ < 30000) { fd_motor(fd, 1); fdc_out(fdc, NE7CMD_SENSED); /* Sense Drive Status */ DELAY(100); fdc_out(fdc, fdu); /* Drive number */ DELAY(100); if ((fdc_in(fdc, &status) == 0) && (status & NE7_ST3_RD)) { fdctl_wr(fdc, FDC_DMAE | FDC_MTON); DELAY(10); return (0); } } return (-1); } static void pc98_fd_check_type(struct fd_data *fd, int unit) { struct fdc_data *fdc; if (fd->type != FDT_NONE || unit < 0 || unit > 3) return; fdc = fd->fdc; /* Look up what the BIOS thinks we have. */ if (!((PC98_SYSTEM_PARAMETER(0x55c) >> unit) & 0x01)) { fd->type = FDT_NONE; return; } if ((PC98_SYSTEM_PARAMETER(0x5ae) >> unit) & 0x01) { /* Check 3mode I/F */ fd->pc98_trans = 0; fdregwr(fdc, FDEM, (unit << 5) | 0x10); if (!(fdregrd(fdc, FDEM) & 0x01)) { fd->type = FDT_144M; return; } device_printf(fd->dev, "Warning: can't control 3mode I/F, fallback to 2mode.\n"); } fd->type = FDT_12M; } #endif /* PC98 */ static void fdc_reset(struct fdc_data *fdc) { int i, r[10]; #ifdef PC98 set_density(fdc); if (pc98_machine_type & M_EPSON_PC98) fdctl_wr(fdc, FDC_RST | FDC_RDY | FDC_DD | FDC_MTON); else fdctl_wr(fdc, FDC_RST | FDC_RDY | FDC_DMAE | FDC_MTON); DELAY(200); fdctl_wr(fdc, FDC_DMAE | FDC_MTON); DELAY(10); #else if (fdc->fdct == FDC_ENHANCED) { /* Try a software reset, default precomp, and 500 kb/s */ fddsr_wr(fdc, I8207X_DSR_SR); } else { /* Try a hardware reset, keep motor on */ fdout_wr(fdc, fdc->fdout & ~(FDO_FRST|FDO_FDMAEN)); DELAY(100); /* enable FDC, but defer interrupts a moment */ fdout_wr(fdc, fdc->fdout & ~FDO_FDMAEN); } DELAY(100); fdout_wr(fdc, fdc->fdout); #endif /* XXX after a reset, silently believe the FDC will accept commands */ if (fdc_cmd(fdc, 3, NE7CMD_SPECIFY, spec1, spec2, 0)) device_printf(fdc->fdc_dev, " SPECIFY failed in reset\n"); if (fdc->fdct == FDC_ENHANCED) { if (fdc_cmd(fdc, 4, I8207X_CONFIG, 0, /* 0x40 | */ /* Enable Implied Seek - * breaks 2step! */ 0x10 | /* Polling disabled */ (fifo_threshold - 1), /* Fifo threshold */ 0x00, /* Precomp track */ 0)) device_printf(fdc->fdc_dev, " CONFIGURE failed in reset\n"); if (debugflags & 1) { if (fdc_cmd(fdc, 1, I8207X_DUMPREG, 10, &r[0], &r[1], &r[2], &r[3], &r[4], &r[5], &r[6], &r[7], &r[8], &r[9])) device_printf(fdc->fdc_dev, " DUMPREG failed in reset\n"); for (i = 0; i < 10; i++) printf(" %02x", r[i]); printf("\n"); } } } static int fdc_sense_drive(struct fdc_data *fdc, int *st3p) { int st3; if (fdc_cmd(fdc, 2, NE7CMD_SENSED, fdc->fd->fdsu, 1, &st3)) return (fdc_err(fdc, "Sense Drive Status failed\n")); if (st3p) *st3p = st3; return (0); } static int fdc_sense_int(struct fdc_data *fdc, int *st0p, int *cylp) { int cyl, st0, ret; ret = fdc_cmd(fdc, 1, NE7CMD_SENSEI, 1, &st0); if (ret) { (void)fdc_err(fdc, "sense intr err reading stat reg 0\n"); return (ret); } if (st0p) *st0p = st0; if ((st0 & NE7_ST0_IC) == NE7_ST0_IC_IV) { /* * There doesn't seem to have been an interrupt. */ return (FD_NOT_VALID); } if (fdc_in(fdc, &cyl) < 0) return fdc_err(fdc, "can't get cyl num\n"); if (cylp) *cylp = cyl; return (0); } static int fdc_read_status(struct fdc_data *fdc) { int i, ret, status; for (i = ret = 0; i < 7; i++) { ret = fdc_in(fdc, &status); fdc->status[i] = status; if (ret != 0) break; } if (ret == 0) fdc->flags |= FDC_STAT_VALID; else fdc->flags &= ~FDC_STAT_VALID; return ret; } #ifndef PC98 /* * Select this drive */ static void fd_select(struct fd_data *fd) { struct fdc_data *fdc; /* XXX: lock controller */ fdc = fd->fdc; fdc->fdout &= ~FDO_FDSEL; fdc->fdout |= FDO_FDMAEN | FDO_FRST | fd->fdsu; fdout_wr(fdc, fdc->fdout); } static void fd_turnon(void *arg) { struct fd_data *fd; struct bio *bp; int once; fd = arg; mtx_assert(&fd->fdc->fdc_mtx, MA_OWNED); fd->flags &= ~FD_MOTORWAIT; fd->flags |= FD_MOTOR; once = 0; for (;;) { bp = bioq_takefirst(&fd->fd_bq); if (bp == NULL) break; bioq_disksort(&fd->fdc->head, bp); once = 1; } if (once) wakeup(&fd->fdc->head); } #endif static void fd_motor(struct fd_data *fd, int turnon) { struct fdc_data *fdc; fdc = fd->fdc; /* mtx_assert(&fdc->fdc_mtx, MA_OWNED); */ #ifdef PC98 fdregwr(fdc, FDP, (pc98_trans != 1 ? FDP_FDDEXC : 0) | FDP_PORTEXC); DELAY(10); fdctl_wr(fdc, FDC_DMAE | FDC_MTON); #else if (turnon) { fd->flags |= FD_MOTORWAIT; fdc->fdout |= (FDO_MOEN0 << fd->fdsu); callout_reset(&fd->toffhandle, hz, fd_turnon, fd); } else { callout_stop(&fd->toffhandle); fd->flags &= ~(FD_MOTOR|FD_MOTORWAIT); fdc->fdout &= ~(FDO_MOEN0 << fd->fdsu); } fdout_wr(fdc, fdc->fdout); #endif } static void fd_turnoff(void *xfd) { struct fd_data *fd = xfd; mtx_assert(&fd->fdc->fdc_mtx, MA_OWNED); fd_motor(fd, 0); } /* * fdc_intr - wake up the worker thread. */ static void fdc_intr(void *arg) { wakeup(arg); } static int fdc_intr_fast(void *arg) { wakeup(arg); return(FILTER_HANDLED); } /* * fdc_pio(): perform programmed IO read/write for YE PCMCIA floppy. */ static void fdc_pio(struct fdc_data *fdc) { u_char *cptr; struct bio *bp; u_int count; bp = fdc->bp; cptr = fdc->fd->fd_ioptr; count = fdc->fd->fd_iosize; if (bp->bio_cmd == BIO_READ) { fdbcdr_wr(fdc, 0, count); bus_space_read_multi_1(fdc->iot, fdc->ioh[FD_YE_DATAPORT], fdc->ioff[FD_YE_DATAPORT], cptr, count); } else { bus_space_write_multi_1(fdc->iot, fdc->ioh[FD_YE_DATAPORT], fdc->ioff[FD_YE_DATAPORT], cptr, count); fdbcdr_wr(fdc, 0, count); /* needed? */ } } static int fdc_biodone(struct fdc_data *fdc, int error) { struct fd_data *fd; struct bio *bp; fd = fdc->fd; bp = fdc->bp; mtx_lock(&fdc->fdc_mtx); if (--fd->fd_iocount == 0) callout_reset(&fd->toffhandle, 4 * hz, fd_turnoff, fd); fdc->bp = NULL; fdc->fd = NULL; mtx_unlock(&fdc->fdc_mtx); if (bp->bio_to != NULL) { if ((debugflags & 2) && fd->fdc->retry > 0) printf("retries: %d\n", fd->fdc->retry); g_io_deliver(bp, error); return (0); } bp->bio_error = error; bp->bio_flags |= BIO_DONE; wakeup(bp); return (0); } static int retry_line; static int fdc_worker(struct fdc_data *fdc) { struct fd_data *fd; struct bio *bp; int i, nsect; int st0, st3, cyl, mfm, steptrac, cylinder, descyl, sec; int head; int override_error; static int need_recal; struct fdc_readid *idp; struct fd_formb *finfo; override_error = 0; /* Have we exhausted our retries ? */ bp = fdc->bp; fd = fdc->fd; if (bp != NULL && (fdc->retry >= retries || (fd->options & FDOPT_NORETRY))) { if ((debugflags & 4)) printf("Too many retries (EIO)\n"); if (fdc->flags & FDC_NEEDS_RESET) { mtx_lock(&fdc->fdc_mtx); fd->flags |= FD_EMPTY; mtx_unlock(&fdc->fdc_mtx); } return (fdc_biodone(fdc, EIO)); } /* Disable ISADMA if we bailed while it was active */ if (fd != NULL && (fd->flags & FD_ISADMA)) { isa_dmadone( bp->bio_cmd == BIO_READ ? ISADMA_READ : ISADMA_WRITE, fd->fd_ioptr, fd->fd_iosize, fdc->dmachan); mtx_lock(&fdc->fdc_mtx); fd->flags &= ~FD_ISADMA; mtx_unlock(&fdc->fdc_mtx); } /* Unwedge the controller ? */ if (fdc->flags & FDC_NEEDS_RESET) { fdc->flags &= ~FDC_NEEDS_RESET; fdc_reset(fdc); tsleep(fdc, PRIBIO, "fdcrst", hz); /* Discard results */ for (i = 0; i < 4; i++) fdc_sense_int(fdc, &st0, &cyl); /* All drives must recal */ need_recal = 0xf; } /* Pick up a request, if need be wait for it */ if (fdc->bp == NULL) { mtx_lock(&fdc->fdc_mtx); do { fdc->bp = bioq_takefirst(&fdc->head); if (fdc->bp == NULL) msleep(&fdc->head, &fdc->fdc_mtx, - PRIBIO, "-", hz); + PRIBIO, "-", 0); } while (fdc->bp == NULL && (fdc->flags & FDC_KTHREAD_EXIT) == 0); mtx_unlock(&fdc->fdc_mtx); if (fdc->bp == NULL) /* * Nothing to do, worker thread has been * requested to stop. */ return (0); bp = fdc->bp; fd = fdc->fd = bp->bio_driver1; fdc->retry = 0; fd->fd_ioptr = bp->bio_data; if (bp->bio_cmd == BIO_FMT) { i = offsetof(struct fd_formb, fd_formb_cylno(0)); fd->fd_ioptr += i; fd->fd_iosize = bp->bio_length - i; } } /* Select drive, setup params */ #ifdef PC98 pc98_trans = fd->ft->trans; if (pc98_trans_prev != pc98_trans) { int i; set_density(fdc); for (i = 0; i < 10; i++) { outb(0x5f, 0); outb(0x5f, 0); } pc98_trans_prev = pc98_trans; } if (pc98_trans != fd->pc98_trans) { if (fd->type == FDT_144M) { fdregwr(fdc, FDEM, (device_get_unit(fd->dev) << 5) | 0x10 | (pc98_trans >> 1)); outb(0x5f, 0); outb(0x5f, 0); } fd->pc98_trans = pc98_trans; } #else fd_select(fd); if (fdc->fdct == FDC_ENHANCED) fddsr_wr(fdc, fd->ft->trans); else fdctl_wr(fdc, fd->ft->trans); #endif if (bp->bio_cmd == BIO_PROBE) { if ((!(device_get_flags(fd->dev) & FD_NO_CHLINE) && #ifndef PC98 !(fdin_rd(fdc) & FDI_DCHG) && #endif !(fd->flags & FD_EMPTY)) || fd_probe_disk(fd, &need_recal) == 0) return (fdc_biodone(fdc, 0)); return (1); } /* * If we are dead just flush the requests */ if (fd->flags & FD_EMPTY) return (fdc_biodone(fdc, ENXIO)); #ifndef PC98 /* Check if we lost our media */ if (fdin_rd(fdc) & FDI_DCHG) { if (debugflags & 0x40) printf("Lost disk\n"); mtx_lock(&fdc->fdc_mtx); fd->flags |= FD_EMPTY; fd->flags |= FD_NEWDISK; mtx_unlock(&fdc->fdc_mtx); g_topology_lock(); g_orphan_provider(fd->fd_provider, ENXIO); fd->fd_provider->flags |= G_PF_WITHER; fd->fd_provider = g_new_providerf(fd->fd_geom, "%s", fd->fd_geom->name); g_error_provider(fd->fd_provider, 0); g_topology_unlock(); return (fdc_biodone(fdc, ENXIO)); } #endif /* Check if the floppy is write-protected */ if (bp->bio_cmd == BIO_FMT || bp->bio_cmd == BIO_WRITE) { retry_line = __LINE__; if(fdc_sense_drive(fdc, &st3) != 0) return (1); if(st3 & NE7_ST3_WP) return (fdc_biodone(fdc, EROFS)); } mfm = (fd->ft->flags & FL_MFM)? NE7CMD_MFM: 0; steptrac = (fd->ft->flags & FL_2STEP)? 2: 1; i = fd->ft->sectrac * fd->ft->heads; cylinder = bp->bio_pblkno / i; descyl = cylinder * steptrac; sec = bp->bio_pblkno % i; nsect = i - sec; head = sec / fd->ft->sectrac; sec = sec % fd->ft->sectrac + 1; /* If everything is going swimmingly, use multisector xfer */ if (fdc->retry == 0 && (bp->bio_cmd == BIO_READ || bp->bio_cmd == BIO_WRITE)) { fd->fd_iosize = imin(nsect * fd->sectorsize, bp->bio_resid); nsect = fd->fd_iosize / fd->sectorsize; } else if (bp->bio_cmd == BIO_READ || bp->bio_cmd == BIO_WRITE) { fd->fd_iosize = fd->sectorsize; nsect = 1; } /* Do RECAL if we need to or are going to track zero anyway */ if ((need_recal & (1 << fd->fdsu)) || (cylinder == 0 && fd->track != 0) || fdc->retry > 2) { #ifdef PC98 pc98_fd_check_ready(fd); #endif retry_line = __LINE__; if (fdc_cmd(fdc, 2, NE7CMD_RECAL, fd->fdsu, 0)) return (1); tsleep(fdc, PRIBIO, "fdrecal", hz); retry_line = __LINE__; if (fdc_sense_int(fdc, &st0, &cyl) == FD_NOT_VALID) return (1); /* XXX */ retry_line = __LINE__; if ((st0 & 0xc0) || cyl != 0) return (1); need_recal &= ~(1 << fd->fdsu); fd->track = 0; /* let the heads settle */ if (settle) tsleep(fdc->fd, PRIBIO, "fdhdstl", settle); } /* * SEEK to where we want to be */ if (cylinder != fd->track) { #ifdef PC98 pc98_fd_check_ready(fd); #endif retry_line = __LINE__; if (fdc_cmd(fdc, 3, NE7CMD_SEEK, fd->fdsu, descyl, 0)) return (1); tsleep(fdc, PRIBIO, "fdseek", hz); retry_line = __LINE__; if (fdc_sense_int(fdc, &st0, &cyl) == FD_NOT_VALID) return (1); /* XXX */ retry_line = __LINE__; if ((st0 & 0xc0) || cyl != descyl) { need_recal |= (1 << fd->fdsu); return (1); } /* let the heads settle */ if (settle) tsleep(fdc->fd, PRIBIO, "fdhdstl", settle); } fd->track = cylinder; if (debugflags & 8) printf("op %x bn %ju siz %u ptr %p retry %d\n", bp->bio_cmd, bp->bio_pblkno, fd->fd_iosize, fd->fd_ioptr, fdc->retry); /* Setup ISADMA if we need it and have it */ if ((bp->bio_cmd == BIO_READ || bp->bio_cmd == BIO_WRITE || bp->bio_cmd == BIO_FMT) && !(fdc->flags & FDC_NODMA)) { isa_dmastart( bp->bio_cmd == BIO_READ ? ISADMA_READ : ISADMA_WRITE, fd->fd_ioptr, fd->fd_iosize, fdc->dmachan); mtx_lock(&fdc->fdc_mtx); fd->flags |= FD_ISADMA; mtx_unlock(&fdc->fdc_mtx); } /* Do PIO if we have to */ if (fdc->flags & FDC_NODMA) { if (bp->bio_cmd == BIO_READ || bp->bio_cmd == BIO_WRITE || bp->bio_cmd == BIO_FMT) fdbcdr_wr(fdc, 1, fd->fd_iosize); if (bp->bio_cmd == BIO_WRITE || bp->bio_cmd == BIO_FMT) fdc_pio(fdc); } switch(bp->bio_cmd) { case BIO_FMT: /* formatting */ finfo = (struct fd_formb *)bp->bio_data; retry_line = __LINE__; if (fdc_cmd(fdc, 6, NE7CMD_FORMAT | mfm, head << 2 | fd->fdsu, finfo->fd_formb_secshift, finfo->fd_formb_nsecs, finfo->fd_formb_gaplen, finfo->fd_formb_fillbyte, 0)) return (1); break; case BIO_RDID: retry_line = __LINE__; if (fdc_cmd(fdc, 2, NE7CMD_READID | mfm, head << 2 | fd->fdsu, 0)) return (1); break; case BIO_READ: retry_line = __LINE__; if (fdc_cmd(fdc, 9, NE7CMD_READ | NE7CMD_SK | mfm | NE7CMD_MT, head << 2 | fd->fdsu, /* head & unit */ fd->track, /* track */ head, /* head */ sec, /* sector + 1 */ fd->ft->secsize, /* sector size */ fd->ft->sectrac, /* sectors/track */ fd->ft->gap, /* gap size */ fd->ft->datalen, /* data length */ 0)) return (1); break; case BIO_WRITE: retry_line = __LINE__; if (fdc_cmd(fdc, 9, NE7CMD_WRITE | mfm | NE7CMD_MT, head << 2 | fd->fdsu, /* head & unit */ fd->track, /* track */ head, /* head */ sec, /* sector + 1 */ fd->ft->secsize, /* sector size */ fd->ft->sectrac, /* sectors/track */ fd->ft->gap, /* gap size */ fd->ft->datalen, /* data length */ 0)) return (1); break; default: KASSERT(0 == 1, ("Wrong bio_cmd %x\n", bp->bio_cmd)); } /* Wait for interrupt */ i = tsleep(fdc, PRIBIO, "fddata", hz); /* PIO if the read looks good */ if (i == 0 && (fdc->flags & FDC_NODMA) && (bp->bio_cmd == BIO_READ)) fdc_pio(fdc); /* Finish DMA */ if (fd->flags & FD_ISADMA) { isa_dmadone( bp->bio_cmd == BIO_READ ? ISADMA_READ : ISADMA_WRITE, fd->fd_ioptr, fd->fd_iosize, fdc->dmachan); mtx_lock(&fdc->fdc_mtx); fd->flags &= ~FD_ISADMA; mtx_unlock(&fdc->fdc_mtx); } if (i != 0) { /* * Timeout. * * Due to IBM's brain-dead design, the FDC has a faked ready * signal, hardwired to ready == true. Thus, any command * issued if there's no diskette in the drive will _never_ * complete, and must be aborted by resetting the FDC. * Many thanks, Big Blue! */ retry_line = __LINE__; fdc->flags |= FDC_NEEDS_RESET; return (1); } retry_line = __LINE__; if (fdc_read_status(fdc)) return (1); if (debugflags & 0x10) printf(" -> %x %x %x %x\n", fdc->status[0], fdc->status[1], fdc->status[2], fdc->status[3]); st0 = fdc->status[0] & NE7_ST0_IC; if (st0 != 0) { retry_line = __LINE__; if (st0 == NE7_ST0_IC_AT && fdc->status[1] & NE7_ST1_OR) { /* * DMA overrun. Someone hogged the bus and * didn't release it in time for the next * FDC transfer. */ return (1); } retry_line = __LINE__; if(st0 == NE7_ST0_IC_IV) { fdc->flags |= FDC_NEEDS_RESET; return (1); } retry_line = __LINE__; if(st0 == NE7_ST0_IC_AT && fdc->status[2] & NE7_ST2_WC) { need_recal |= (1 << fd->fdsu); return (1); } if (debugflags & 0x20) { printf("status %02x %02x %02x %02x %02x %02x\n", fdc->status[0], fdc->status[1], fdc->status[2], fdc->status[3], fdc->status[4], fdc->status[5]); } retry_line = __LINE__; if (fd->options & FDOPT_NOERROR) override_error = 1; else return (1); } /* All OK */ switch(bp->bio_cmd) { case BIO_RDID: /* copy out ID field contents */ idp = (struct fdc_readid *)bp->bio_data; idp->cyl = fdc->status[3]; idp->head = fdc->status[4]; idp->sec = fdc->status[5]; idp->secshift = fdc->status[6]; if (debugflags & 0x40) printf("c %d h %d s %d z %d\n", idp->cyl, idp->head, idp->sec, idp->secshift); break; case BIO_READ: case BIO_WRITE: bp->bio_pblkno += nsect; bp->bio_resid -= fd->fd_iosize; bp->bio_completed += fd->fd_iosize; fd->fd_ioptr += fd->fd_iosize; if (override_error) { if ((debugflags & 4)) printf("FDOPT_NOERROR: returning bad data\n"); } else { /* Since we managed to get something done, * reset the retry */ fdc->retry = 0; if (bp->bio_resid > 0) return (0); } break; case BIO_FMT: break; } return (fdc_biodone(fdc, 0)); } static void fdc_thread(void *arg) { struct fdc_data *fdc; fdc = arg; int i; mtx_lock(&fdc->fdc_mtx); fdc->flags |= FDC_KTHREAD_ALIVE; while ((fdc->flags & FDC_KTHREAD_EXIT) == 0) { mtx_unlock(&fdc->fdc_mtx); i = fdc_worker(fdc); if (i && debugflags & 0x20) { if (fdc->bp != NULL) { g_print_bio(fdc->bp); printf("\n"); } printf("Retry line %d\n", retry_line); } fdc->retry += i; mtx_lock(&fdc->fdc_mtx); } fdc->flags &= ~(FDC_KTHREAD_EXIT | FDC_KTHREAD_ALIVE); mtx_unlock(&fdc->fdc_mtx); kproc_exit(0); } /* * Enqueue a request. */ static void fd_enqueue(struct fd_data *fd, struct bio *bp) { struct fdc_data *fdc; int call; call = 0; fdc = fd->fdc; mtx_lock(&fdc->fdc_mtx); /* If we go from idle, cancel motor turnoff */ if (fd->fd_iocount++ == 0) callout_stop(&fd->toffhandle); if (fd->flags & FD_MOTOR) { /* The motor is on, send it directly to the controller */ bioq_disksort(&fdc->head, bp); wakeup(&fdc->head); } else { /* Queue it on the drive until the motor has started */ bioq_insert_tail(&fd->fd_bq, bp); if (!(fd->flags & FD_MOTORWAIT)) fd_motor(fd, 1); } mtx_unlock(&fdc->fdc_mtx); } /* * Try to find out if we have a disk in the drive. */ static int fd_probe_disk(struct fd_data *fd, int *recal) { struct fdc_data *fdc; int st0, st3, cyl; int oopts, ret; fdc = fd->fdc; oopts = fd->options; fd->options |= FDOPT_NOERRLOG | FDOPT_NORETRY; ret = 1; /* * First recal, then seek to cyl#1, this clears the old condition on * the disk change line so we can examine it for current status. */ if (debugflags & 0x40) printf("New disk in probe\n"); mtx_lock(&fdc->fdc_mtx); fd->flags |= FD_NEWDISK; mtx_unlock(&fdc->fdc_mtx); if (fdc_cmd(fdc, 2, NE7CMD_RECAL, fd->fdsu, 0)) goto done; tsleep(fdc, PRIBIO, "fdrecal", hz); if (fdc_sense_int(fdc, &st0, &cyl) == FD_NOT_VALID) goto done; /* XXX */ if ((st0 & 0xc0) || cyl != 0) goto done; /* Seek to track 1 */ if (fdc_cmd(fdc, 3, NE7CMD_SEEK, fd->fdsu, 1, 0)) goto done; tsleep(fdc, PRIBIO, "fdseek", hz); if (fdc_sense_int(fdc, &st0, &cyl) == FD_NOT_VALID) goto done; /* XXX */ *recal |= (1 << fd->fdsu); #ifndef PC98 if (fdin_rd(fdc) & FDI_DCHG) { if (debugflags & 0x40) printf("Empty in probe\n"); mtx_lock(&fdc->fdc_mtx); fd->flags |= FD_EMPTY; mtx_unlock(&fdc->fdc_mtx); } else { #else { #endif if (fdc_sense_drive(fdc, &st3) != 0) goto done; if (debugflags & 0x40) printf("Got disk in probe\n"); mtx_lock(&fdc->fdc_mtx); fd->flags &= ~FD_EMPTY; if (st3 & NE7_ST3_WP) fd->flags |= FD_WP; else fd->flags &= ~FD_WP; mtx_unlock(&fdc->fdc_mtx); } ret = 0; done: fd->options = oopts; return (ret); } static int fdmisccmd(struct fd_data *fd, u_int cmd, void *data) { struct bio *bp; struct fd_formb *finfo; struct fdc_readid *idfield; int error; bp = malloc(sizeof(struct bio), M_TEMP, M_WAITOK | M_ZERO); /* * Set up a bio request for fdstrategy(). bio_offset is faked * so that fdstrategy() will seek to the requested * cylinder, and use the desired head. */ bp->bio_cmd = cmd; if (cmd == BIO_FMT) { finfo = (struct fd_formb *)data; bp->bio_pblkno = (finfo->cyl * fd->ft->heads + finfo->head) * fd->ft->sectrac; bp->bio_length = sizeof *finfo; } else if (cmd == BIO_RDID) { idfield = (struct fdc_readid *)data; bp->bio_pblkno = (idfield->cyl * fd->ft->heads + idfield->head) * fd->ft->sectrac; bp->bio_length = sizeof(struct fdc_readid); } else if (cmd == BIO_PROBE) { /* nothing */ } else panic("wrong cmd in fdmisccmd()"); bp->bio_offset = bp->bio_pblkno * fd->sectorsize; bp->bio_data = data; bp->bio_driver1 = fd; bp->bio_flags = 0; fd_enqueue(fd, bp); do { tsleep(bp, PRIBIO, "fdwait", hz); } while (!(bp->bio_flags & BIO_DONE)); error = bp->bio_error; free(bp, M_TEMP); return (error); } /* * Try figuring out the density of the media present in our device. */ static int fdautoselect(struct fd_data *fd) { struct fd_type *fdtp; struct fdc_readid id; int oopts, rv; if (!(fd->ft->flags & FL_AUTO)) return (0); fdtp = fd_native_types[fd->type]; fdsettype(fd, fdtp); if (!(fd->ft->flags & FL_AUTO)) return (0); /* * Try reading sector ID fields, first at cylinder 0, head 0, * then at cylinder 2, head N. We don't probe cylinder 1, * since for 5.25in DD media in a HD drive, there are no data * to read (2 step pulses per media cylinder required). For * two-sided media, the second probe always goes to head 1, so * we can tell them apart from single-sided media. As a * side-effect this means that single-sided media should be * mentioned in the search list after two-sided media of an * otherwise identical density. Media with a different number * of sectors per track but otherwise identical parameters * cannot be distinguished at all. * * If we successfully read an ID field on both cylinders where * the recorded values match our expectation, we are done. * Otherwise, we try the next density entry from the table. * * Stepping to cylinder 2 has the side-effect of clearing the * unit attention bit. */ oopts = fd->options; fd->options |= FDOPT_NOERRLOG | FDOPT_NORETRY; for (; fdtp->heads; fdtp++) { fdsettype(fd, fdtp); id.cyl = id.head = 0; rv = fdmisccmd(fd, BIO_RDID, &id); if (rv != 0) continue; if (id.cyl != 0 || id.head != 0 || id.secshift != fdtp->secsize) continue; id.cyl = 2; id.head = fd->ft->heads - 1; rv = fdmisccmd(fd, BIO_RDID, &id); if (id.cyl != 2 || id.head != fdtp->heads - 1 || id.secshift != fdtp->secsize) continue; if (rv == 0) break; } fd->options = oopts; if (fdtp->heads == 0) { if (debugflags & 0x40) device_printf(fd->dev, "autoselection failed\n"); fdsettype(fd, fd_native_types[fd->type]); return (-1); } else { if (debugflags & 0x40) { device_printf(fd->dev, "autoselected %d KB medium\n", #ifdef PC98 (128 << (fd->ft->secsize)) * fd->ft->size / 1024); #else fd->ft->size / 2); #endif fdprinttype(fd->ft); } return (0); } } /* * GEOM class implementation */ static g_access_t fd_access; static g_start_t fd_start; static g_ioctl_t fd_ioctl; struct g_class g_fd_class = { .name = "FD", .version = G_VERSION, .start = fd_start, .access = fd_access, .ioctl = fd_ioctl, }; static int fd_access(struct g_provider *pp, int r, int w, int e) { struct fd_data *fd; struct fdc_data *fdc; int ar, aw, ae; int busy; fd = pp->geom->softc; fdc = fd->fdc; /* * If our provider is withering, we can only get negative requests * and we don't want to even see them */ if (pp->flags & G_PF_WITHER) return (0); ar = r + pp->acr; aw = w + pp->acw; ae = e + pp->ace; if (ar == 0 && aw == 0 && ae == 0) { fd->options &= ~(FDOPT_NORETRY | FDOPT_NOERRLOG | FDOPT_NOERROR); device_unbusy(fd->dev); return (0); } busy = 0; if (pp->acr == 0 && pp->acw == 0 && pp->ace == 0) { #ifdef PC98 if (pc98_fd_check_ready(fd) == -1) return (ENXIO); #endif if (fdmisccmd(fd, BIO_PROBE, NULL)) return (ENXIO); if (fd->flags & FD_EMPTY) return (ENXIO); if (fd->flags & FD_NEWDISK) { if (fdautoselect(fd) != 0 && (device_get_flags(fd->dev) & FD_NO_CHLINE)) { mtx_lock(&fdc->fdc_mtx); fd->flags |= FD_EMPTY; mtx_unlock(&fdc->fdc_mtx); return (ENXIO); } mtx_lock(&fdc->fdc_mtx); fd->flags &= ~FD_NEWDISK; mtx_unlock(&fdc->fdc_mtx); } device_busy(fd->dev); busy = 1; } if (w > 0 && (fd->flags & FD_WP)) { if (busy) device_unbusy(fd->dev); return (EROFS); } pp->sectorsize = fd->sectorsize; pp->stripesize = fd->ft->heads * fd->ft->sectrac * fd->sectorsize; pp->mediasize = pp->stripesize * fd->ft->tracks; return (0); } static void fd_start(struct bio *bp) { struct fdc_data * fdc; struct fd_data * fd; fd = bp->bio_to->geom->softc; fdc = fd->fdc; bp->bio_driver1 = fd; if (bp->bio_cmd == BIO_GETATTR) { if (g_handleattr_int(bp, "GEOM::fwsectors", fd->ft->sectrac)) return; if (g_handleattr_int(bp, "GEOM::fwheads", fd->ft->heads)) return; g_io_deliver(bp, ENOIOCTL); return; } if (!(bp->bio_cmd == BIO_READ || bp->bio_cmd == BIO_WRITE)) { g_io_deliver(bp, EOPNOTSUPP); return; } bp->bio_pblkno = bp->bio_offset / fd->sectorsize; bp->bio_resid = bp->bio_length; fd_enqueue(fd, bp); return; } static int fd_ioctl(struct g_provider *pp, u_long cmd, void *data, int fflag, struct thread *td) { struct fd_data *fd; struct fdc_status *fsp; struct fdc_readid *rid; int error; fd = pp->geom->softc; #ifdef PC98 pc98_fd_check_ready(fd); #endif switch (cmd) { case FD_GTYPE: /* get drive type */ *(struct fd_type *)data = *fd->ft; return (0); case FD_STYPE: /* set drive type */ /* * Allow setting drive type temporarily iff * currently unset. Used for fdformat so any * user can set it, and then start formatting. */ fd->fts = *(struct fd_type *)data; if (fd->fts.sectrac) { /* XXX: check for rubbish */ fdsettype(fd, &fd->fts); } else { fdsettype(fd, fd_native_types[fd->type]); } if (debugflags & 0x40) fdprinttype(fd->ft); return (0); case FD_GOPTS: /* get drive options */ *(int *)data = fd->options; return (0); case FD_SOPTS: /* set drive options */ fd->options = *(int *)data; return (0); case FD_CLRERR: error = priv_check(td, PRIV_DRIVER); if (error) return (error); fd->fdc->fdc_errs = 0; return (0); case FD_GSTAT: fsp = (struct fdc_status *)data; if ((fd->fdc->flags & FDC_STAT_VALID) == 0) return (EINVAL); memcpy(fsp->status, fd->fdc->status, 7 * sizeof(u_int)); return (0); case FD_GDTYPE: *(enum fd_drivetype *)data = fd->type; return (0); case FD_FORM: if (!(fflag & FWRITE)) return (EPERM); if (((struct fd_formb *)data)->format_version != FD_FORMAT_VERSION) return (EINVAL); /* wrong version of formatting prog */ error = fdmisccmd(fd, BIO_FMT, data); mtx_lock(&fd->fdc->fdc_mtx); fd->flags |= FD_NEWDISK; mtx_unlock(&fd->fdc->fdc_mtx); break; case FD_READID: rid = (struct fdc_readid *)data; if (rid->cyl > 85 || rid->head > 1) return (EINVAL); error = fdmisccmd(fd, BIO_RDID, data); break; case FIONBIO: case FIOASYNC: /* For backwards compat with old fd*(8) tools */ error = 0; break; default: if (debugflags & 0x80) printf("Unknown ioctl %lx\n", cmd); error = ENOIOCTL; break; } return (error); }; /* * Configuration/initialization stuff, per controller. */ devclass_t fdc_devclass; static devclass_t fd_devclass; struct fdc_ivars { int fdunit; int fdtype; }; void fdc_release_resources(struct fdc_data *fdc) { device_t dev; struct resource *last; int i; dev = fdc->fdc_dev; if (fdc->fdc_intr) bus_teardown_intr(dev, fdc->res_irq, fdc->fdc_intr); fdc->fdc_intr = NULL; if (fdc->res_irq != NULL) bus_release_resource(dev, SYS_RES_IRQ, fdc->rid_irq, fdc->res_irq); fdc->res_irq = NULL; last = NULL; for (i = 0; i < FDC_MAXREG; i++) { if (fdc->resio[i] != NULL && fdc->resio[i] != last) { bus_release_resource(dev, SYS_RES_IOPORT, fdc->ridio[i], fdc->resio[i]); last = fdc->resio[i]; fdc->resio[i] = NULL; } } if (fdc->res_drq != NULL) bus_release_resource(dev, SYS_RES_DRQ, fdc->rid_drq, fdc->res_drq); fdc->res_drq = NULL; } int fdc_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct fdc_ivars *ivars = device_get_ivars(child); switch (which) { case FDC_IVAR_FDUNIT: *result = ivars->fdunit; break; case FDC_IVAR_FDTYPE: *result = ivars->fdtype; break; default: return (ENOENT); } return (0); } int fdc_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { struct fdc_ivars *ivars = device_get_ivars(child); switch (which) { case FDC_IVAR_FDUNIT: ivars->fdunit = value; break; case FDC_IVAR_FDTYPE: ivars->fdtype = value; break; default: return (ENOENT); } return (0); } int fdc_initial_reset(device_t dev, struct fdc_data *fdc) { int ic_type, part_id; #ifdef PC98 /* See if it can handle a command. */ if (fdc_cmd(fdc, 3, NE7CMD_SPECIFY, NE7_SPEC_1(4, 240), NE7_SPEC_2(2, 0), 0)) return (ENXIO); #else /* * A status value of 0xff is very unlikely, but not theoretically * impossible, but it is far more likely to indicate an empty bus. */ if (fdsts_rd(fdc) == 0xff) return (ENXIO); /* * Assert a reset to the floppy controller and check that the status * register goes to zero. */ fdout_wr(fdc, 0); fdout_wr(fdc, 0); if (fdsts_rd(fdc) != 0) return (ENXIO); /* * Clear the reset and see it come ready. */ fdout_wr(fdc, FDO_FRST); DELAY(100); if (fdsts_rd(fdc) != 0x80) return (ENXIO); /* Then, see if it can handle a command. */ if (fdc_cmd(fdc, 3, NE7CMD_SPECIFY, NE7_SPEC_1(6, 240), NE7_SPEC_2(31, 0), 0)) return (ENXIO); #endif /* * Try to identify the chip. * * The i8272 datasheet documents that unknown commands * will return ST0 as 0x80. The i8272 is supposedly identical * to the NEC765. * The i82077SL datasheet says 0x90 for the VERSION command, * and several "superio" chips emulate this. */ if (fdc_cmd(fdc, 1, NE7CMD_VERSION, 1, &ic_type)) return (ENXIO); if (fdc_cmd(fdc, 1, 0x18, 1, &part_id)) return (ENXIO); if (bootverbose) device_printf(dev, "ic_type %02x part_id %02x\n", ic_type, part_id); switch (ic_type & 0xff) { case 0x80: device_set_desc(dev, "NEC 765 or clone"); fdc->fdct = FDC_NE765; break; case 0x81: case 0x90: device_set_desc(dev, "Enhanced floppy controller"); fdc->fdct = FDC_ENHANCED; break; default: device_set_desc(dev, "Generic floppy controller"); fdc->fdct = FDC_UNKNOWN; break; } return (0); } int fdc_detach(device_t dev) { struct fdc_data *fdc; int error; fdc = device_get_softc(dev); /* have our children detached first */ if ((error = bus_generic_detach(dev))) return (error); if (fdc->fdc_intr) bus_teardown_intr(dev, fdc->res_irq, fdc->fdc_intr); fdc->fdc_intr = NULL; /* kill worker thread */ mtx_lock(&fdc->fdc_mtx); fdc->flags |= FDC_KTHREAD_EXIT; wakeup(&fdc->head); while ((fdc->flags & FDC_KTHREAD_ALIVE) != 0) msleep(fdc->fdc_thread, &fdc->fdc_mtx, PRIBIO, "fdcdet", 0); mtx_unlock(&fdc->fdc_mtx); /* reset controller, turn motor off */ #ifdef PC98 fdc_reset(fdc); #else fdout_wr(fdc, 0); #endif if (!(fdc->flags & FDC_NODMA)) isa_dma_release(fdc->dmachan); fdc_release_resources(fdc); mtx_destroy(&fdc->fdc_mtx); return (0); } /* * Add a child device to the fdc controller. It will then be probed etc. */ device_t fdc_add_child(device_t dev, const char *name, int unit) { struct fdc_ivars *ivar; device_t child; ivar = malloc(sizeof *ivar, M_DEVBUF /* XXX */, M_NOWAIT | M_ZERO); if (ivar == NULL) return (NULL); child = device_add_child(dev, name, unit); if (child == NULL) { free(ivar, M_DEVBUF); return (NULL); } device_set_ivars(child, ivar); ivar->fdunit = unit; ivar->fdtype = FDT_NONE; if (resource_disabled(name, unit)) device_disable(child); return (child); } int fdc_attach(device_t dev) { struct fdc_data *fdc; int error; fdc = device_get_softc(dev); fdc->fdc_dev = dev; error = fdc_initial_reset(dev, fdc); if (error) { device_printf(dev, "does not respond\n"); return (error); } error = bus_setup_intr(dev, fdc->res_irq, INTR_TYPE_BIO | INTR_ENTROPY | ((fdc->flags & FDC_NOFAST) ? INTR_MPSAFE : 0), ((fdc->flags & FDC_NOFAST) ? NULL : fdc_intr_fast), ((fdc->flags & FDC_NOFAST) ? fdc_intr : NULL), fdc, &fdc->fdc_intr); if (error) { device_printf(dev, "cannot setup interrupt\n"); return (error); } if (!(fdc->flags & FDC_NODMA)) { error = isa_dma_acquire(fdc->dmachan); if (!error) { error = isa_dma_init(fdc->dmachan, MAX_BYTES_PER_CYL, M_WAITOK); if (error) isa_dma_release(fdc->dmachan); } if (error) return (error); } fdc->fdcu = device_get_unit(dev); fdc->flags |= FDC_NEEDS_RESET; mtx_init(&fdc->fdc_mtx, "fdc lock", NULL, MTX_DEF); /* reset controller, turn motor off, clear fdout mirror reg */ #ifdef PC98 fdc_reset(fdc); #else fdout_wr(fdc, fdc->fdout = 0); #endif bioq_init(&fdc->head); kproc_create(fdc_thread, fdc, &fdc->fdc_thread, 0, 0, "fdc%d", device_get_unit(dev)); settle = hz / 8; return (0); } int fdc_hints_probe(device_t dev) { const char *name, *dname; int i, error, dunit; /* * Probe and attach any children. We should probably detect * devices from the BIOS unless overridden. */ name = device_get_nameunit(dev); i = 0; while ((resource_find_match(&i, &dname, &dunit, "at", name)) == 0) { resource_int_value(dname, dunit, "drive", &dunit); fdc_add_child(dev, dname, dunit); } if ((error = bus_generic_attach(dev)) != 0) return (error); return (0); } int fdc_print_child(device_t me, device_t child) { int retval = 0, flags; retval += bus_print_child_header(me, child); retval += printf(" on %s drive %d", device_get_nameunit(me), fdc_get_fdunit(child)); if ((flags = device_get_flags(me)) != 0) retval += printf(" flags %#x", flags); retval += printf("\n"); return (retval); } /* * Configuration/initialization, per drive. */ static int fd_probe(device_t dev) { int unit; #ifndef PC98 int i; u_int st0, st3; #endif struct fd_data *fd; struct fdc_data *fdc; int fdsu; int flags, type; fdsu = fdc_get_fdunit(dev); fd = device_get_softc(dev); fdc = device_get_softc(device_get_parent(dev)); flags = device_get_flags(dev); fd->dev = dev; fd->fdc = fdc; fd->fdsu = fdsu; unit = device_get_unit(dev); /* Auto-probe if fdinfo is present, but always allow override. */ type = flags & FD_TYPEMASK; if (type == FDT_NONE && (type = fdc_get_fdtype(dev)) != FDT_NONE) { fd->type = type; goto done; } else { /* make sure fdautoselect() will be called */ fd->flags = FD_EMPTY; fd->type = type; } #ifdef PC98 pc98_fd_check_type(fd, unit); #elif defined(__i386__) || defined(__amd64__) if (fd->type == FDT_NONE && (unit == 0 || unit == 1)) { /* Look up what the BIOS thinks we have. */ if (unit == 0) fd->type = (rtcin(RTC_FDISKETTE) & 0xf0) >> 4; else fd->type = rtcin(RTC_FDISKETTE) & 0x0f; if (fd->type == FDT_288M_1) fd->type = FDT_288M; } #endif /* __i386__ || __amd64__ */ /* is there a unit? */ if (fd->type == FDT_NONE) return (ENXIO); #ifndef PC98 /* mtx_lock(&fdc->fdc_mtx); */ /* select it */ fd_select(fd); fd_motor(fd, 1); fdc->fd = fd; fdc_reset(fdc); /* XXX reset, then unreset, etc. */ DELAY(1000000); /* 1 sec */ if ((flags & FD_NO_PROBE) == 0) { /* If we're at track 0 first seek inwards. */ if ((fdc_sense_drive(fdc, &st3) == 0) && (st3 & NE7_ST3_T0)) { /* Seek some steps... */ if (fdc_cmd(fdc, 3, NE7CMD_SEEK, fdsu, 10, 0) == 0) { /* ...wait a moment... */ DELAY(300000); /* make ctrlr happy: */ fdc_sense_int(fdc, NULL, NULL); } } for (i = 0; i < 2; i++) { /* * we must recalibrate twice, just in case the * heads have been beyond cylinder 76, since * most FDCs still barf when attempting to * recalibrate more than 77 steps */ /* go back to 0: */ if (fdc_cmd(fdc, 2, NE7CMD_RECAL, fdsu, 0) == 0) { /* a second being enough for full stroke seek*/ DELAY(i == 0 ? 1000000 : 300000); /* anything responding? */ if (fdc_sense_int(fdc, &st0, NULL) == 0 && (st0 & NE7_ST0_EC) == 0) break; /* already probed succesfully */ } } } fd_motor(fd, 0); fdc->fd = NULL; /* mtx_unlock(&fdc->fdc_mtx); */ if ((flags & FD_NO_PROBE) == 0 && (st0 & NE7_ST0_EC) != 0) /* no track 0 -> no drive present */ return (ENXIO); #endif /* PC98 */ done: switch (fd->type) { #ifdef PC98 case FDT_144M: device_set_desc(dev, "1.44M FDD"); break; case FDT_12M: device_set_desc(dev, "1M/640K FDD"); break; #else case FDT_12M: device_set_desc(dev, "1200-KB 5.25\" drive"); break; case FDT_144M: device_set_desc(dev, "1440-KB 3.5\" drive"); break; case FDT_288M: device_set_desc(dev, "2880-KB 3.5\" drive (in 1440-KB mode)"); break; case FDT_360K: device_set_desc(dev, "360-KB 5.25\" drive"); break; case FDT_720K: device_set_desc(dev, "720-KB 3.5\" drive"); break; #endif default: return (ENXIO); } fd->track = FD_NO_TRACK; fd->fdc = fdc; fd->fdsu = fdsu; fd->options = 0; #ifdef PC98 fd->pc98_trans = 0; #endif callout_init_mtx(&fd->toffhandle, &fd->fdc->fdc_mtx, 0); /* initialize densities for subdevices */ fdsettype(fd, fd_native_types[fd->type]); return (0); } /* * We have to do this in a geom event because GEOM is not running * when fd_attach() is. * XXX: move fd_attach after geom like ata/scsi disks */ static void fd_attach2(void *arg, int flag) { struct fd_data *fd; fd = arg; fd->fd_geom = g_new_geomf(&g_fd_class, "fd%d", device_get_unit(fd->dev)); fd->fd_provider = g_new_providerf(fd->fd_geom, "%s", fd->fd_geom->name); fd->fd_geom->softc = fd; g_error_provider(fd->fd_provider, 0); } static int fd_attach(device_t dev) { struct fd_data *fd; fd = device_get_softc(dev); g_post_event(fd_attach2, fd, M_WAITOK, NULL); fd->flags |= FD_EMPTY; bioq_init(&fd->fd_bq); return (0); } static void fd_detach_geom(void *arg, int flag) { struct fd_data *fd = arg; g_topology_assert(); g_wither_geom(fd->fd_geom, ENXIO); } static int fd_detach(device_t dev) { struct fd_data *fd; fd = device_get_softc(dev); g_waitfor_event(fd_detach_geom, fd, M_WAITOK, NULL); while (device_get_state(dev) == DS_BUSY) tsleep(fd, PZERO, "fdd", hz/10); callout_drain(&fd->toffhandle); return (0); } static device_method_t fd_methods[] = { /* Device interface */ DEVMETHOD(device_probe, fd_probe), DEVMETHOD(device_attach, fd_attach), DEVMETHOD(device_detach, fd_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), /* XXX */ DEVMETHOD(device_resume, bus_generic_resume), /* XXX */ { 0, 0 } }; static driver_t fd_driver = { "fd", fd_methods, sizeof(struct fd_data) }; static int fdc_modevent(module_t mod, int type, void *data) { return (g_modevent(NULL, type, &g_fd_class)); } DRIVER_MODULE(fd, fdc, fd_driver, fd_devclass, fdc_modevent, 0); Index: projects/release-pkg/sys/dev/iicbus/ds1307.c =================================================================== --- projects/release-pkg/sys/dev/iicbus/ds1307.c (revision 297604) +++ projects/release-pkg/sys/dev/iicbus/ds1307.c (revision 297605) @@ -1,411 +1,436 @@ /*- * Copyright (c) 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$"); /* * Driver for Maxim DS1307 I2C real-time clock/calendar. */ #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #include #endif #include #include "clock_if.h" #include "iicbus_if.h" struct ds1307_softc { device_t sc_dev; int sc_year0; struct intr_config_hook enum_hook; uint16_t sc_addr; /* DS1307 slave address. */ uint8_t sc_ctrl; + int sc_mcp7941x; }; static void ds1307_start(void *); +#ifdef FDT +static const struct ofw_compat_data ds1307_compat_data[] = { + {"dallas,ds1307", (uintptr_t)"Maxim DS1307 RTC"}, + {"maxim,ds1307", (uintptr_t)"Maxim DS1307 RTC"}, + {"microchip,mcp7941x", (uintptr_t)"Microchip MCP7941x RTC"}, + { NULL, 0 } +}; +#endif + static int ds1307_read(device_t dev, uint16_t addr, uint8_t reg, uint8_t *data, size_t len) { struct iic_msg msg[2] = { { addr, IIC_M_WR | IIC_M_NOSTOP, 1, ® }, { addr, IIC_M_RD, len, data }, }; return (iicbus_transfer(dev, msg, nitems(msg))); } static int ds1307_write(device_t dev, uint16_t addr, uint8_t *data, size_t len) { struct iic_msg msg[1] = { { addr, IIC_M_WR, len, data }, }; return (iicbus_transfer(dev, msg, nitems(msg))); } static int ds1307_ctrl_read(struct ds1307_softc *sc) { int error; sc->sc_ctrl = 0; error = ds1307_read(sc->sc_dev, sc->sc_addr, DS1307_CONTROL, &sc->sc_ctrl, sizeof(sc->sc_ctrl)); if (error) { device_printf(sc->sc_dev, "cannot read from RTC.\n"); return (error); } return (0); } static int ds1307_ctrl_write(struct ds1307_softc *sc) { int error; uint8_t data[2]; data[0] = DS1307_CONTROL; data[1] = sc->sc_ctrl & DS1307_CTRL_MASK; error = ds1307_write(sc->sc_dev, sc->sc_addr, data, sizeof(data)); if (error != 0) device_printf(sc->sc_dev, "cannot write to RTC.\n"); return (error); } static int ds1307_osc_enable(struct ds1307_softc *sc) { int error; uint8_t data[2], secs; secs = 0; error = ds1307_read(sc->sc_dev, sc->sc_addr, DS1307_SECS, &secs, sizeof(secs)); if (error) { device_printf(sc->sc_dev, "cannot read from RTC.\n"); return (error); } /* Check if the oscillator is disabled. */ if ((secs & DS1307_SECS_CH) == 0) return (0); device_printf(sc->sc_dev, "clock was halted, check the battery.\n"); data[0] = DS1307_SECS; data[1] = secs & DS1307_SECS_MASK; error = ds1307_write(sc->sc_dev, sc->sc_addr, data, sizeof(data)); if (error != 0) device_printf(sc->sc_dev, "cannot write to RTC.\n"); return (error); } static int ds1307_set_24hrs_mode(struct ds1307_softc *sc) { int error; uint8_t data[2], hour; hour = 0; error = ds1307_read(sc->sc_dev, sc->sc_addr, DS1307_HOUR, &hour, sizeof(hour)); if (error) { device_printf(sc->sc_dev, "cannot read from RTC.\n"); return (error); } data[0] = DS1307_HOUR; data[1] = hour & DS1307_HOUR_MASK; error = ds1307_write(sc->sc_dev, sc->sc_addr, data, sizeof(data)); if (error != 0) device_printf(sc->sc_dev, "cannot write to RTC.\n"); return (error); } static int ds1307_sqwe_sysctl(SYSCTL_HANDLER_ARGS) { - int sqwe, error, newv; + int sqwe, error, newv, sqwe_bit; struct ds1307_softc *sc; sc = (struct ds1307_softc *)arg1; error = ds1307_ctrl_read(sc); if (error != 0) return (error); - sqwe = newv = (sc->sc_ctrl & DS1307_CTRL_SQWE) ? 1 : 0; + if (sc->sc_mcp7941x) + sqwe_bit = MCP7941X_CTRL_SQWE; + else + sqwe_bit = DS1307_CTRL_SQWE; + sqwe = newv = (sc->sc_ctrl & sqwe_bit) ? 1 : 0; error = sysctl_handle_int(oidp, &newv, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (sqwe != newv) { - sc->sc_ctrl &= ~DS1307_CTRL_SQWE; + sc->sc_ctrl &= ~sqwe_bit; if (newv) - sc->sc_ctrl |= DS1307_CTRL_SQWE; + sc->sc_ctrl |= sqwe_bit; error = ds1307_ctrl_write(sc); if (error != 0) return (error); } return (error); } static int ds1307_sqw_freq_sysctl(SYSCTL_HANDLER_ARGS) { int ds1307_sqw_freq[] = { 1, 4096, 8192, 32768 }; int error, freq, i, newf, tmp; struct ds1307_softc *sc; sc = (struct ds1307_softc *)arg1; error = ds1307_ctrl_read(sc); if (error != 0) return (error); tmp = (sc->sc_ctrl & DS1307_CTRL_RS_MASK); if (tmp >= nitems(ds1307_sqw_freq)) tmp = nitems(ds1307_sqw_freq) - 1; freq = ds1307_sqw_freq[tmp]; error = sysctl_handle_int(oidp, &freq, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (freq != ds1307_sqw_freq[tmp]) { newf = 0; for (i = 0; i < nitems(ds1307_sqw_freq); i++) if (freq >= ds1307_sqw_freq[i]) newf = i; sc->sc_ctrl &= ~DS1307_CTRL_RS_MASK; sc->sc_ctrl |= newf; error = ds1307_ctrl_write(sc); if (error != 0) return (error); } return (error); } static int ds1307_sqw_out_sysctl(SYSCTL_HANDLER_ARGS) { int sqwe, error, newv; struct ds1307_softc *sc; sc = (struct ds1307_softc *)arg1; error = ds1307_ctrl_read(sc); if (error != 0) return (error); sqwe = newv = (sc->sc_ctrl & DS1307_CTRL_OUT) ? 1 : 0; error = sysctl_handle_int(oidp, &newv, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (sqwe != newv) { sc->sc_ctrl &= ~DS1307_CTRL_OUT; if (newv) sc->sc_ctrl |= DS1307_CTRL_OUT; error = ds1307_ctrl_write(sc); if (error != 0) return (error); } return (error); } static int ds1307_probe(device_t dev) { - #ifdef FDT + const struct ofw_compat_data *compat; + if (!ofw_bus_status_okay(dev)) return (ENXIO); - if (!ofw_bus_is_compatible(dev, "dallas,ds1307") && - !ofw_bus_is_compatible(dev, "maxim,ds1307")) + + compat = ofw_bus_search_compatible(dev, ds1307_compat_data); + + if (compat == NULL) return (ENXIO); -#endif + + device_set_desc(dev, (const char *)compat->ocd_data); + + return (BUS_PROBE_DEFAULT); +#else device_set_desc(dev, "Maxim DS1307 RTC"); return (BUS_PROBE_DEFAULT); +#endif } static int ds1307_attach(device_t dev) { struct ds1307_softc *sc; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_addr = iicbus_get_addr(dev); sc->sc_year0 = 1900; sc->enum_hook.ich_func = ds1307_start; sc->enum_hook.ich_arg = dev; + + if (ofw_bus_is_compatible(dev, "microchip,mcp7941x")) + sc->sc_mcp7941x = 1; /* * We have to wait until interrupts are enabled. Usually I2C read * and write only works when the interrupts are available. */ if (config_intrhook_establish(&sc->enum_hook) != 0) return (ENOMEM); return (0); } static void ds1307_start(void *xdev) { device_t dev; struct ds1307_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree_node; struct sysctl_oid_list *tree; dev = (device_t)xdev; sc = device_get_softc(dev); ctx = device_get_sysctl_ctx(dev); tree_node = device_get_sysctl_tree(dev); tree = SYSCTL_CHILDREN(tree_node); config_intrhook_disestablish(&sc->enum_hook); /* Set the 24 hours mode. */ if (ds1307_set_24hrs_mode(sc) != 0) return; /* Enable the oscillator if halted. */ if (ds1307_osc_enable(sc) != 0) return; /* Configuration parameters. */ SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "sqwe", CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, sc, 0, ds1307_sqwe_sysctl, "IU", "DS1307 square-wave enable"); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "sqw_freq", CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, sc, 0, ds1307_sqw_freq_sysctl, "IU", "DS1307 square-wave output frequency"); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "sqw_out", CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, sc, 0, ds1307_sqw_out_sysctl, "IU", "DS1307 square-wave output state"); /* 1 second resolution. */ clock_register(dev, 1000000); } static int ds1307_gettime(device_t dev, struct timespec *ts) { int error; struct clocktime ct; struct ds1307_softc *sc; uint8_t data[7]; sc = device_get_softc(dev); memset(data, 0, sizeof(data)); error = ds1307_read(sc->sc_dev, sc->sc_addr, DS1307_SECS, data, sizeof(data)); if (error != 0) { device_printf(dev, "cannot read from RTC.\n"); return (error); } ct.nsec = 0; ct.sec = FROMBCD(data[DS1307_SECS] & DS1307_SECS_MASK); ct.min = FROMBCD(data[DS1307_MINS] & DS1307_MINS_MASK); ct.hour = FROMBCD(data[DS1307_HOUR] & DS1307_HOUR_MASK); ct.day = FROMBCD(data[DS1307_DATE] & DS1307_DATE_MASK); ct.dow = data[DS1307_WEEKDAY] & DS1307_WEEKDAY_MASK; ct.mon = FROMBCD(data[DS1307_MONTH] & DS1307_MONTH_MASK); ct.year = FROMBCD(data[DS1307_YEAR] & DS1307_YEAR_MASK); ct.year += sc->sc_year0; if (ct.year < POSIX_BASE_YEAR) ct.year += 100; /* assume [1970, 2069] */ return (clock_ct_to_ts(&ct, ts)); } static int ds1307_settime(device_t dev, struct timespec *ts) { int error; struct clocktime ct; struct ds1307_softc *sc; uint8_t data[8]; sc = device_get_softc(dev); /* Accuracy is only one second. */ if (ts->tv_nsec >= 500000000) ts->tv_sec++; ts->tv_nsec = 0; clock_ts_to_ct(ts, &ct); memset(data, 0, sizeof(data)); data[0] = DS1307_SECS; data[DS1307_SECS + 1] = TOBCD(ct.sec); data[DS1307_MINS + 1] = TOBCD(ct.min); data[DS1307_HOUR + 1] = TOBCD(ct.hour); data[DS1307_DATE + 1] = TOBCD(ct.day); data[DS1307_WEEKDAY + 1] = ct.dow; data[DS1307_MONTH + 1] = TOBCD(ct.mon); data[DS1307_YEAR + 1] = TOBCD(ct.year % 100); /* Write the time back to RTC. */ error = ds1307_write(dev, sc->sc_addr, data, sizeof(data)); if (error != 0) device_printf(dev, "cannot write to RTC.\n"); return (error); } static device_method_t ds1307_methods[] = { DEVMETHOD(device_probe, ds1307_probe), DEVMETHOD(device_attach, ds1307_attach), DEVMETHOD(clock_gettime, ds1307_gettime), DEVMETHOD(clock_settime, ds1307_settime), DEVMETHOD_END }; static driver_t ds1307_driver = { "ds1307", ds1307_methods, sizeof(struct ds1307_softc), }; static devclass_t ds1307_devclass; DRIVER_MODULE(ds1307, iicbus, ds1307_driver, ds1307_devclass, NULL, NULL); MODULE_VERSION(ds1307, 1); MODULE_DEPEND(ds1307, iicbus, 1, 1, 1); Index: projects/release-pkg/sys/dev/iicbus/ds1307reg.h =================================================================== --- projects/release-pkg/sys/dev/iicbus/ds1307reg.h (revision 297604) +++ projects/release-pkg/sys/dev/iicbus/ds1307reg.h (revision 297605) @@ -1,59 +1,60 @@ /*- * Copyright (c) 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. * * $FreeBSD$ */ /* * Maxim DS1307 RTC registers. */ #ifndef _DS1307REG_H_ #define _DS1307REG_H_ #define DS1307_SECS 0x00 #define DS1307_SECS_MASK 0x7f #define DS1307_SECS_CH 0x80 #define DS1307_MINS 0x01 #define DS1307_MINS_MASK 0x7f #define DS1307_HOUR 0x02 #define DS1307_HOUR_MASK 0x3f #define DS1307_WEEKDAY 0x03 #define DS1307_WEEKDAY_MASK 0x07 #define DS1307_DATE 0x04 #define DS1307_DATE_MASK 0x3f #define DS1307_MONTH 0x05 #define DS1307_MONTH_MASK 0x1f #define DS1307_YEAR 0x06 #define DS1307_YEAR_MASK 0xff #define DS1307_CONTROL 0x07 #define DS1307_CTRL_OUT (1 << 7) +#define MCP7941X_CTRL_SQWE (1 << 6) #define DS1307_CTRL_SQWE (1 << 4) #define DS1307_CTRL_RS1 (1 << 1) #define DS1307_CTRL_RS0 (1 << 0) #define DS1307_CTRL_RS_MASK (DS1307_CTRL_RS1 | DS1307_CTRL_RS0) #define DS1307_CTRL_MASK 0x93 #endif /* _DS1307REG_H_ */ Index: projects/release-pkg/sys/dev/urtwn/if_urtwn.c =================================================================== --- projects/release-pkg/sys/dev/urtwn/if_urtwn.c (revision 297604) +++ projects/release-pkg/sys/dev/urtwn/if_urtwn.c (revision 297605) @@ -1,5402 +1,5477 @@ /* $OpenBSD: if_urtwn.c,v 1.16 2011/02/10 17:26:40 jakemsr Exp $ */ /*- * Copyright (c) 2010 Damien Bergamini * Copyright (c) 2014 Kevin Lo * Copyright (c) 2015 Andriy Voskoboinyk * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); /* * Driver for Realtek RTL8188CE-VAU/RTL8188CUS/RTL8188EU/RTL8188RU/RTL8192CU. */ #include "opt_wlan.h" #include "opt_urtwn.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 #include #include #include #include #include #include #include #include #include #include #include #include #include +#ifdef IEEE80211_SUPPORT_SUPERG +#include +#endif #include #include #include #include "usbdevs.h" #include #include #include #ifdef USB_DEBUG enum { URTWN_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ URTWN_DEBUG_RECV = 0x00000002, /* basic recv operation */ URTWN_DEBUG_STATE = 0x00000004, /* 802.11 state transitions */ URTWN_DEBUG_RA = 0x00000008, /* f/w rate adaptation setup */ URTWN_DEBUG_USB = 0x00000010, /* usb requests */ URTWN_DEBUG_FIRMWARE = 0x00000020, /* firmware(9) loading debug */ URTWN_DEBUG_BEACON = 0x00000040, /* beacon handling */ URTWN_DEBUG_INTR = 0x00000080, /* ISR */ URTWN_DEBUG_TEMP = 0x00000100, /* temperature calibration */ URTWN_DEBUG_ROM = 0x00000200, /* various ROM info */ URTWN_DEBUG_KEY = 0x00000400, /* crypto keys management */ URTWN_DEBUG_TXPWR = 0x00000800, /* dump Tx power values */ URTWN_DEBUG_RSSI = 0x00001000, /* dump RSSI lookups */ URTWN_DEBUG_ANY = 0xffffffff }; #define URTWN_DPRINTF(_sc, _m, ...) do { \ if ((_sc)->sc_debug & (_m)) \ device_printf((_sc)->sc_dev, __VA_ARGS__); \ } while(0) #else #define URTWN_DPRINTF(_sc, _m, ...) do { (void) sc; } while (0) #endif #define IEEE80211_HAS_ADDR4(wh) IEEE80211_IS_DSTODS(wh) static int urtwn_enable_11n = 1; TUNABLE_INT("hw.usb.urtwn.enable_11n", &urtwn_enable_11n); /* various supported device vendors/products */ static const STRUCT_USB_HOST_ID urtwn_devs[] = { #define URTWN_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } #define URTWN_RTL8188E_DEV(v,p) \ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, URTWN_RTL8188E) } #define URTWN_RTL8188E 1 URTWN_DEV(ABOCOM, RTL8188CU_1), URTWN_DEV(ABOCOM, RTL8188CU_2), URTWN_DEV(ABOCOM, RTL8192CU), URTWN_DEV(ASUS, RTL8192CU), URTWN_DEV(ASUS, USBN10NANO), URTWN_DEV(AZUREWAVE, RTL8188CE_1), URTWN_DEV(AZUREWAVE, RTL8188CE_2), URTWN_DEV(AZUREWAVE, RTL8188CU), URTWN_DEV(BELKIN, F7D2102), URTWN_DEV(BELKIN, RTL8188CU), URTWN_DEV(BELKIN, RTL8192CU), URTWN_DEV(CHICONY, RTL8188CUS_1), URTWN_DEV(CHICONY, RTL8188CUS_2), URTWN_DEV(CHICONY, RTL8188CUS_3), URTWN_DEV(CHICONY, RTL8188CUS_4), URTWN_DEV(CHICONY, RTL8188CUS_5), URTWN_DEV(COREGA, RTL8192CU), URTWN_DEV(DLINK, RTL8188CU), URTWN_DEV(DLINK, RTL8192CU_1), URTWN_DEV(DLINK, RTL8192CU_2), URTWN_DEV(DLINK, RTL8192CU_3), URTWN_DEV(DLINK, DWA131B), URTWN_DEV(EDIMAX, EW7811UN), URTWN_DEV(EDIMAX, RTL8192CU), URTWN_DEV(FEIXUN, RTL8188CU), URTWN_DEV(FEIXUN, RTL8192CU), URTWN_DEV(GUILLEMOT, HWNUP150), URTWN_DEV(HAWKING, RTL8192CU), URTWN_DEV(HP3, RTL8188CU), URTWN_DEV(NETGEAR, WNA1000M), URTWN_DEV(NETGEAR, RTL8192CU), URTWN_DEV(NETGEAR4, RTL8188CU), URTWN_DEV(NOVATECH, RTL8188CU), URTWN_DEV(PLANEX2, RTL8188CU_1), URTWN_DEV(PLANEX2, RTL8188CU_2), URTWN_DEV(PLANEX2, RTL8188CU_3), URTWN_DEV(PLANEX2, RTL8188CU_4), URTWN_DEV(PLANEX2, RTL8188CUS), URTWN_DEV(PLANEX2, RTL8192CU), URTWN_DEV(REALTEK, RTL8188CE_0), URTWN_DEV(REALTEK, RTL8188CE_1), URTWN_DEV(REALTEK, RTL8188CTV), URTWN_DEV(REALTEK, RTL8188CU_0), URTWN_DEV(REALTEK, RTL8188CU_1), URTWN_DEV(REALTEK, RTL8188CU_2), URTWN_DEV(REALTEK, RTL8188CU_3), URTWN_DEV(REALTEK, RTL8188CU_COMBO), URTWN_DEV(REALTEK, RTL8188CUS), URTWN_DEV(REALTEK, RTL8188RU_1), URTWN_DEV(REALTEK, RTL8188RU_2), URTWN_DEV(REALTEK, RTL8188RU_3), URTWN_DEV(REALTEK, RTL8191CU), URTWN_DEV(REALTEK, RTL8192CE), URTWN_DEV(REALTEK, RTL8192CU), URTWN_DEV(SITECOMEU, RTL8188CU_1), URTWN_DEV(SITECOMEU, RTL8188CU_2), URTWN_DEV(SITECOMEU, RTL8192CU), URTWN_DEV(TRENDNET, RTL8188CU), URTWN_DEV(TRENDNET, RTL8192CU), URTWN_DEV(ZYXEL, RTL8192CU), /* URTWN_RTL8188E */ URTWN_RTL8188E_DEV(ABOCOM, RTL8188EU), URTWN_RTL8188E_DEV(DLINK, DWA123D1), URTWN_RTL8188E_DEV(DLINK, DWA125D1), URTWN_RTL8188E_DEV(ELECOM, WDC150SU2M), URTWN_RTL8188E_DEV(REALTEK, RTL8188ETV), URTWN_RTL8188E_DEV(REALTEK, RTL8188EU), #undef URTWN_RTL8188E_DEV #undef URTWN_DEV }; static device_probe_t urtwn_match; static device_attach_t urtwn_attach; static device_detach_t urtwn_detach; static usb_callback_t urtwn_bulk_tx_callback; static usb_callback_t urtwn_bulk_rx_callback; static void urtwn_sysctlattach(struct urtwn_softc *); static void urtwn_drain_mbufq(struct urtwn_softc *); static usb_error_t urtwn_do_request(struct urtwn_softc *, struct usb_device_request *, void *); static struct ieee80211vap *urtwn_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 urtwn_vap_delete(struct ieee80211vap *); static struct mbuf * urtwn_rx_copy_to_mbuf(struct urtwn_softc *, struct r92c_rx_stat *, int); static struct mbuf * urtwn_report_intr(struct usb_xfer *, struct urtwn_data *); static struct mbuf * urtwn_rxeof(struct urtwn_softc *, uint8_t *, int); static void urtwn_r88e_ratectl_tx_complete(struct urtwn_softc *, void *); static struct ieee80211_node *urtwn_rx_frame(struct urtwn_softc *, struct mbuf *, int8_t *); static void urtwn_txeof(struct urtwn_softc *, struct urtwn_data *, int); static int urtwn_alloc_list(struct urtwn_softc *, struct urtwn_data[], int, int); static int urtwn_alloc_rx_list(struct urtwn_softc *); static int urtwn_alloc_tx_list(struct urtwn_softc *); static void urtwn_free_list(struct urtwn_softc *, struct urtwn_data data[], int); static void urtwn_free_rx_list(struct urtwn_softc *); static void urtwn_free_tx_list(struct urtwn_softc *); static struct urtwn_data * _urtwn_getbuf(struct urtwn_softc *); static struct urtwn_data * urtwn_getbuf(struct urtwn_softc *); static usb_error_t urtwn_write_region_1(struct urtwn_softc *, uint16_t, uint8_t *, int); static usb_error_t urtwn_write_1(struct urtwn_softc *, uint16_t, uint8_t); static usb_error_t urtwn_write_2(struct urtwn_softc *, uint16_t, uint16_t); static usb_error_t urtwn_write_4(struct urtwn_softc *, uint16_t, uint32_t); static usb_error_t urtwn_read_region_1(struct urtwn_softc *, uint16_t, uint8_t *, int); static uint8_t urtwn_read_1(struct urtwn_softc *, uint16_t); static uint16_t urtwn_read_2(struct urtwn_softc *, uint16_t); static uint32_t urtwn_read_4(struct urtwn_softc *, uint16_t); static int urtwn_fw_cmd(struct urtwn_softc *, uint8_t, const void *, int); static void urtwn_cmdq_cb(void *, int); static int urtwn_cmd_sleepable(struct urtwn_softc *, const void *, size_t, CMD_FUNC_PROTO); static void urtwn_r92c_rf_write(struct urtwn_softc *, int, uint8_t, uint32_t); static void urtwn_r88e_rf_write(struct urtwn_softc *, int, uint8_t, uint32_t); static uint32_t urtwn_rf_read(struct urtwn_softc *, int, uint8_t); static int urtwn_llt_write(struct urtwn_softc *, uint32_t, uint32_t); static int urtwn_efuse_read_next(struct urtwn_softc *, uint8_t *); static int urtwn_efuse_read_data(struct urtwn_softc *, uint8_t *, uint8_t, uint8_t); #ifdef USB_DEBUG static void urtwn_dump_rom_contents(struct urtwn_softc *, uint8_t *, uint16_t); #endif static int urtwn_efuse_read(struct urtwn_softc *, uint8_t *, uint16_t); static int urtwn_efuse_switch_power(struct urtwn_softc *); static int urtwn_read_chipid(struct urtwn_softc *); static int urtwn_read_rom(struct urtwn_softc *); static int urtwn_r88e_read_rom(struct urtwn_softc *); static int urtwn_ra_init(struct urtwn_softc *); static void urtwn_init_beacon(struct urtwn_softc *, struct urtwn_vap *); static int urtwn_setup_beacon(struct urtwn_softc *, struct ieee80211_node *); static void urtwn_update_beacon(struct ieee80211vap *, int); static int urtwn_tx_beacon(struct urtwn_softc *sc, struct urtwn_vap *); static int urtwn_key_alloc(struct ieee80211vap *, struct ieee80211_key *, ieee80211_keyix *, ieee80211_keyix *); static void urtwn_key_set_cb(struct urtwn_softc *, union sec_param *); static void urtwn_key_del_cb(struct urtwn_softc *, union sec_param *); static int urtwn_key_set(struct ieee80211vap *, const struct ieee80211_key *); static int urtwn_key_delete(struct ieee80211vap *, const struct ieee80211_key *); static void urtwn_tsf_task_adhoc(void *, int); static void urtwn_tsf_sync_enable(struct urtwn_softc *, struct ieee80211vap *); static void urtwn_get_tsf(struct urtwn_softc *, uint64_t *); static void urtwn_set_led(struct urtwn_softc *, int, int); static void urtwn_set_mode(struct urtwn_softc *, uint8_t); static void urtwn_ibss_recv_mgmt(struct ieee80211_node *, struct mbuf *, int, const struct ieee80211_rx_stats *, int, int); static int urtwn_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void urtwn_calib_to(void *); static void urtwn_calib_cb(struct urtwn_softc *, union sec_param *); static void urtwn_watchdog(void *); static void urtwn_update_avgrssi(struct urtwn_softc *, int, int8_t); static int8_t urtwn_get_rssi(struct urtwn_softc *, int, void *); static int8_t urtwn_r88e_get_rssi(struct urtwn_softc *, int, void *); static int urtwn_tx_data(struct urtwn_softc *, struct ieee80211_node *, struct mbuf *, struct urtwn_data *); static int urtwn_tx_raw(struct urtwn_softc *, struct ieee80211_node *, struct mbuf *, struct urtwn_data *, const struct ieee80211_bpf_params *); static void urtwn_tx_start(struct urtwn_softc *, struct mbuf *, uint8_t, struct urtwn_data *); static int urtwn_transmit(struct ieee80211com *, struct mbuf *); static void urtwn_start(struct urtwn_softc *); static void urtwn_parent(struct ieee80211com *); static int urtwn_r92c_power_on(struct urtwn_softc *); static int urtwn_r88e_power_on(struct urtwn_softc *); static void urtwn_r92c_power_off(struct urtwn_softc *); static void urtwn_r88e_power_off(struct urtwn_softc *); static int urtwn_llt_init(struct urtwn_softc *); #ifndef URTWN_WITHOUT_UCODE static void urtwn_fw_reset(struct urtwn_softc *); static void urtwn_r88e_fw_reset(struct urtwn_softc *); static int urtwn_fw_loadpage(struct urtwn_softc *, int, const uint8_t *, int); static int urtwn_load_firmware(struct urtwn_softc *); #endif static int urtwn_dma_init(struct urtwn_softc *); static int urtwn_mac_init(struct urtwn_softc *); static void urtwn_bb_init(struct urtwn_softc *); static void urtwn_rf_init(struct urtwn_softc *); static void urtwn_cam_init(struct urtwn_softc *); static int urtwn_cam_write(struct urtwn_softc *, uint32_t, uint32_t); static void urtwn_pa_bias_init(struct urtwn_softc *); static void urtwn_rxfilter_init(struct urtwn_softc *); static void urtwn_edca_init(struct urtwn_softc *); static void urtwn_write_txpower(struct urtwn_softc *, int, uint16_t[]); static void urtwn_get_txpower(struct urtwn_softc *, int, struct ieee80211_channel *, struct ieee80211_channel *, uint16_t[]); static void urtwn_r88e_get_txpower(struct urtwn_softc *, int, struct ieee80211_channel *, struct ieee80211_channel *, uint16_t[]); static void urtwn_set_txpower(struct urtwn_softc *, struct ieee80211_channel *, struct ieee80211_channel *); static void urtwn_set_rx_bssid_all(struct urtwn_softc *, int); static void urtwn_set_gain(struct urtwn_softc *, uint8_t); static void urtwn_scan_start(struct ieee80211com *); static void urtwn_scan_end(struct ieee80211com *); static void urtwn_set_channel(struct ieee80211com *); static int urtwn_wme_update(struct ieee80211com *); static void urtwn_update_slot(struct ieee80211com *); static void urtwn_update_slot_cb(struct urtwn_softc *, union sec_param *); static void urtwn_update_aifs(struct urtwn_softc *, uint8_t); static void urtwn_set_promisc(struct urtwn_softc *); static void urtwn_update_promisc(struct ieee80211com *); static void urtwn_update_mcast(struct ieee80211com *); static struct ieee80211_node *urtwn_r88e_node_alloc(struct ieee80211vap *, const uint8_t mac[IEEE80211_ADDR_LEN]); static void urtwn_r88e_newassoc(struct ieee80211_node *, int); static void urtwn_r88e_node_free(struct ieee80211_node *); static void urtwn_set_chan(struct urtwn_softc *, struct ieee80211_channel *, struct ieee80211_channel *); static void urtwn_iq_calib(struct urtwn_softc *); static void urtwn_lc_calib(struct urtwn_softc *); static void urtwn_temp_calib(struct urtwn_softc *); static int urtwn_init(struct urtwn_softc *); static void urtwn_stop(struct urtwn_softc *); static void urtwn_abort_xfers(struct urtwn_softc *); static int urtwn_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void urtwn_ms_delay(struct urtwn_softc *); /* Aliases. */ #define urtwn_bb_write urtwn_write_4 #define urtwn_bb_read urtwn_read_4 static const struct usb_config urtwn_config[URTWN_N_TRANSFER] = { [URTWN_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = URTWN_RXBUFSZ, .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = urtwn_bulk_rx_callback, }, [URTWN_BULK_TX_BE] = { .type = UE_BULK, .endpoint = 0x03, .direction = UE_DIR_OUT, .bufsize = URTWN_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .force_short_xfer = 1 }, .callback = urtwn_bulk_tx_callback, .timeout = URTWN_TX_TIMEOUT, /* ms */ }, [URTWN_BULK_TX_BK] = { .type = UE_BULK, .endpoint = 0x03, .direction = UE_DIR_OUT, .bufsize = URTWN_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .force_short_xfer = 1, }, .callback = urtwn_bulk_tx_callback, .timeout = URTWN_TX_TIMEOUT, /* ms */ }, [URTWN_BULK_TX_VI] = { .type = UE_BULK, .endpoint = 0x02, .direction = UE_DIR_OUT, .bufsize = URTWN_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .force_short_xfer = 1 }, .callback = urtwn_bulk_tx_callback, .timeout = URTWN_TX_TIMEOUT, /* ms */ }, [URTWN_BULK_TX_VO] = { .type = UE_BULK, .endpoint = 0x02, .direction = UE_DIR_OUT, .bufsize = URTWN_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .force_short_xfer = 1 }, .callback = urtwn_bulk_tx_callback, .timeout = URTWN_TX_TIMEOUT, /* ms */ }, }; static const struct wme_to_queue { uint16_t reg; uint8_t qid; } wme2queue[WME_NUM_AC] = { { R92C_EDCA_BE_PARAM, URTWN_BULK_TX_BE}, { R92C_EDCA_BK_PARAM, URTWN_BULK_TX_BK}, { R92C_EDCA_VI_PARAM, URTWN_BULK_TX_VI}, { R92C_EDCA_VO_PARAM, URTWN_BULK_TX_VO} }; static int urtwn_match(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != URTWN_CONFIG_INDEX) return (ENXIO); if (uaa->info.bIfaceIndex != URTWN_IFACE_INDEX) return (ENXIO); return (usbd_lookup_id_by_uaa(urtwn_devs, sizeof(urtwn_devs), uaa)); } static void urtwn_update_chw(struct ieee80211com *ic) { } static int urtwn_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { /* We're driving this ourselves (eventually); don't involve net80211 */ return (0); } static int urtwn_attach(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); struct urtwn_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; uint8_t bands[howmany(IEEE80211_MODE_MAX, 8)]; int error; device_set_usb_desc(self); sc->sc_udev = uaa->device; sc->sc_dev = self; if (USB_GET_DRIVER_INFO(uaa) == URTWN_RTL8188E) sc->chip |= URTWN_CHIP_88E; #ifdef USB_DEBUG int debug; if (resource_int_value(device_get_name(sc->sc_dev), device_get_unit(sc->sc_dev), "debug", &debug) == 0) sc->sc_debug = debug; #endif mtx_init(&sc->sc_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, MTX_DEF); URTWN_CMDQ_LOCK_INIT(sc); URTWN_NT_LOCK_INIT(sc); callout_init(&sc->sc_calib_to, 0); callout_init(&sc->sc_watchdog_ch, 0); mbufq_init(&sc->sc_snd, ifqmaxlen); sc->sc_iface_index = URTWN_IFACE_INDEX; error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, sc->sc_xfer, urtwn_config, URTWN_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(self, "could not allocate USB transfers, " "err=%s\n", usbd_errstr(error)); goto detach; } URTWN_LOCK(sc); error = urtwn_read_chipid(sc); if (error) { device_printf(sc->sc_dev, "unsupported test chip\n"); URTWN_UNLOCK(sc); goto detach; } /* Determine number of Tx/Rx chains. */ if (sc->chip & URTWN_CHIP_92C) { sc->ntxchains = (sc->chip & URTWN_CHIP_92C_1T2R) ? 1 : 2; sc->nrxchains = 2; } else { sc->ntxchains = 1; sc->nrxchains = 1; } if (sc->chip & URTWN_CHIP_88E) error = urtwn_r88e_read_rom(sc); else error = urtwn_read_rom(sc); if (error != 0) { device_printf(sc->sc_dev, "%s: cannot read rom, error %d\n", __func__, error); URTWN_UNLOCK(sc); goto detach; } device_printf(sc->sc_dev, "MAC/BB RTL%s, RF 6052 %dT%dR\n", (sc->chip & URTWN_CHIP_92C) ? "8192CU" : (sc->chip & URTWN_CHIP_88E) ? "8188EU" : (sc->board_type == R92C_BOARD_TYPE_HIGHPA) ? "8188RU" : (sc->board_type == R92C_BOARD_TYPE_MINICARD) ? "8188CE-VAU" : "8188CUS", sc->ntxchains, sc->nrxchains); URTWN_UNLOCK(sc); ic->ic_softc = sc; ic->ic_name = device_get_nameunit(self); ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */ /* set device capabilities */ ic->ic_caps = IEEE80211_C_STA /* station mode */ | IEEE80211_C_MONITOR /* monitor mode */ | IEEE80211_C_IBSS /* adhoc mode */ | IEEE80211_C_HOSTAP /* hostap mode */ | IEEE80211_C_SHPREAMBLE /* short preamble supported */ | IEEE80211_C_SHSLOT /* short slot time supported */ #if 0 | IEEE80211_C_BGSCAN /* capable of bg scanning */ #endif | IEEE80211_C_WPA /* 802.11i */ | IEEE80211_C_WME /* 802.11e */ + | IEEE80211_C_SWAMSDUTX /* Do software A-MSDU TX */ + | IEEE80211_C_FF /* Atheros fast-frames */ ; ic->ic_cryptocaps = IEEE80211_CRYPTO_WEP | IEEE80211_CRYPTO_TKIP | IEEE80211_CRYPTO_AES_CCM; /* Assume they're all 11n capable for now */ if (urtwn_enable_11n) { device_printf(self, "enabling 11n\n"); ic->ic_htcaps = IEEE80211_HTC_HT | +#if 0 IEEE80211_HTC_AMPDU | +#endif IEEE80211_HTC_AMSDU | IEEE80211_HTCAP_MAXAMSDU_3839 | IEEE80211_HTCAP_SMPS_OFF; /* no HT40 just yet */ // ic->ic_htcaps |= IEEE80211_HTCAP_CHWIDTH40; /* XXX TODO: verify chains versus streams for urtwn */ ic->ic_txstream = sc->ntxchains; ic->ic_rxstream = sc->nrxchains; } memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); if (urtwn_enable_11n) setbit(bands, IEEE80211_MODE_11NG); ieee80211_init_channels(ic, NULL, bands); ieee80211_ifattach(ic); ic->ic_raw_xmit = urtwn_raw_xmit; ic->ic_scan_start = urtwn_scan_start; ic->ic_scan_end = urtwn_scan_end; ic->ic_set_channel = urtwn_set_channel; ic->ic_transmit = urtwn_transmit; ic->ic_parent = urtwn_parent; ic->ic_vap_create = urtwn_vap_create; ic->ic_vap_delete = urtwn_vap_delete; ic->ic_wme.wme_update = urtwn_wme_update; ic->ic_updateslot = urtwn_update_slot; ic->ic_update_promisc = urtwn_update_promisc; ic->ic_update_mcast = urtwn_update_mcast; if (sc->chip & URTWN_CHIP_88E) { ic->ic_node_alloc = urtwn_r88e_node_alloc; ic->ic_newassoc = urtwn_r88e_newassoc; sc->sc_node_free = ic->ic_node_free; ic->ic_node_free = urtwn_r88e_node_free; } ic->ic_update_chw = urtwn_update_chw; ic->ic_ampdu_enable = urtwn_ampdu_enable; ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), URTWN_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), URTWN_RX_RADIOTAP_PRESENT); TASK_INIT(&sc->cmdq_task, 0, urtwn_cmdq_cb, sc); urtwn_sysctlattach(sc); if (bootverbose) ieee80211_announce(ic); return (0); detach: urtwn_detach(self); return (ENXIO); /* failure */ } static void urtwn_sysctlattach(struct urtwn_softc *sc) { #ifdef USB_DEBUG struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev); struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev); SYSCTL_ADD_U32(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "debug", CTLFLAG_RW, &sc->sc_debug, sc->sc_debug, "control debugging printfs"); #endif } static int urtwn_detach(device_t self) { struct urtwn_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; unsigned int x; /* Prevent further ioctls. */ URTWN_LOCK(sc); sc->sc_flags |= URTWN_DETACHED; URTWN_UNLOCK(sc); urtwn_stop(sc); callout_drain(&sc->sc_watchdog_ch); callout_drain(&sc->sc_calib_to); /* stop all USB transfers */ usbd_transfer_unsetup(sc->sc_xfer, URTWN_N_TRANSFER); /* Prevent further allocations from RX/TX data lists. */ URTWN_LOCK(sc); STAILQ_INIT(&sc->sc_tx_active); STAILQ_INIT(&sc->sc_tx_inactive); STAILQ_INIT(&sc->sc_tx_pending); STAILQ_INIT(&sc->sc_rx_active); STAILQ_INIT(&sc->sc_rx_inactive); URTWN_UNLOCK(sc); /* drain USB transfers */ for (x = 0; x != URTWN_N_TRANSFER; x++) usbd_transfer_drain(sc->sc_xfer[x]); /* Free data buffers. */ URTWN_LOCK(sc); urtwn_free_tx_list(sc); urtwn_free_rx_list(sc); URTWN_UNLOCK(sc); if (ic->ic_softc == sc) { ieee80211_draintask(ic, &sc->cmdq_task); ieee80211_ifdetach(ic); } URTWN_NT_LOCK_DESTROY(sc); URTWN_CMDQ_LOCK_DESTROY(sc); mtx_destroy(&sc->sc_mtx); return (0); } static void urtwn_drain_mbufq(struct urtwn_softc *sc) { struct mbuf *m; struct ieee80211_node *ni; URTWN_ASSERT_LOCKED(sc); while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; ieee80211_free_node(ni); m_freem(m); } } static usb_error_t urtwn_do_request(struct urtwn_softc *sc, struct usb_device_request *req, void *data) { usb_error_t err; int ntries = 10; URTWN_ASSERT_LOCKED(sc); while (ntries--) { err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 250 /* ms */); if (err == 0) break; URTWN_DPRINTF(sc, URTWN_DEBUG_USB, "%s: control request failed, %s (retries left: %d)\n", __func__, usbd_errstr(err), ntries); usb_pause_mtx(&sc->sc_mtx, hz / 100); } return (err); } static struct ieee80211vap * urtwn_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 urtwn_softc *sc = ic->ic_softc; struct urtwn_vap *uvp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return (NULL); uvp = malloc(sizeof(struct urtwn_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &uvp->vap; /* enable s/w bmiss handling for sta mode */ if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) { /* out of memory */ free(uvp, M_80211_VAP); return (NULL); } if (opmode == IEEE80211_M_HOSTAP || opmode == IEEE80211_M_IBSS) urtwn_init_beacon(sc, uvp); /* override state transition machine */ uvp->newstate = vap->iv_newstate; vap->iv_newstate = urtwn_newstate; vap->iv_update_beacon = urtwn_update_beacon; vap->iv_key_alloc = urtwn_key_alloc; vap->iv_key_set = urtwn_key_set; vap->iv_key_delete = urtwn_key_delete; if (opmode == IEEE80211_M_IBSS) { uvp->recv_mgmt = vap->iv_recv_mgmt; vap->iv_recv_mgmt = urtwn_ibss_recv_mgmt; TASK_INIT(&uvp->tsf_task_adhoc, 0, urtwn_tsf_task_adhoc, vap); } if (URTWN_CHIP_HAS_RATECTL(sc)) ieee80211_ratectl_init(vap); /* complete setup */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return (vap); } static void urtwn_vap_delete(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct urtwn_softc *sc = ic->ic_softc; struct urtwn_vap *uvp = URTWN_VAP(vap); if (uvp->bcn_mbuf != NULL) m_freem(uvp->bcn_mbuf); if (vap->iv_opmode == IEEE80211_M_IBSS) ieee80211_draintask(ic, &uvp->tsf_task_adhoc); if (URTWN_CHIP_HAS_RATECTL(sc)) ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); free(uvp, M_80211_VAP); } static struct mbuf * urtwn_rx_copy_to_mbuf(struct urtwn_softc *sc, struct r92c_rx_stat *stat, int totlen) { struct ieee80211com *ic = &sc->sc_ic; struct mbuf *m; uint32_t rxdw0; int pktlen; /* * don't pass packets to the ieee80211 framework if the driver isn't * RUNNING. */ if (!(sc->sc_flags & URTWN_RUNNING)) return (NULL); rxdw0 = le32toh(stat->rxdw0); if (rxdw0 & (R92C_RXDW0_CRCERR | R92C_RXDW0_ICVERR)) { /* * This should not happen since we setup our Rx filter * to not receive these frames. */ URTWN_DPRINTF(sc, URTWN_DEBUG_RECV, "%s: RX flags error (%s)\n", __func__, rxdw0 & R92C_RXDW0_CRCERR ? "CRC" : "ICV"); goto fail; } pktlen = MS(rxdw0, R92C_RXDW0_PKTLEN); if (pktlen < sizeof(struct ieee80211_frame_ack)) { URTWN_DPRINTF(sc, URTWN_DEBUG_RECV, "%s: frame is too short: %d\n", __func__, pktlen); goto fail; } if (__predict_false(totlen > MCLBYTES)) { /* convert to m_getjcl if this happens */ device_printf(sc->sc_dev, "%s: frame too long: %d (%d)\n", __func__, pktlen, totlen); goto fail; } m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (__predict_false(m == NULL)) { device_printf(sc->sc_dev, "%s: could not allocate RX mbuf\n", __func__); goto fail; } /* Finalize mbuf. */ memcpy(mtod(m, uint8_t *), (uint8_t *)stat, totlen); m->m_pkthdr.len = m->m_len = totlen; return (m); fail: counter_u64_add(ic->ic_ierrors, 1); return (NULL); } static struct mbuf * urtwn_report_intr(struct usb_xfer *xfer, struct urtwn_data *data) { struct urtwn_softc *sc = data->sc; struct ieee80211com *ic = &sc->sc_ic; struct r92c_rx_stat *stat; uint8_t *buf; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); if (len < sizeof(*stat)) { counter_u64_add(ic->ic_ierrors, 1); return (NULL); } buf = data->buf; stat = (struct r92c_rx_stat *)buf; + /* + * For 88E chips we can tie the FF flushing here; + * this is where we do know exactly how deep the + * transmit queue is. + * + * But it won't work for R92 chips, so we can't + * take the easy way out. + */ + if (sc->chip & URTWN_CHIP_88E) { int report_sel = MS(le32toh(stat->rxdw3), R88E_RXDW3_RPT); switch (report_sel) { case R88E_RXDW3_RPT_RX: return (urtwn_rxeof(sc, buf, len)); case R88E_RXDW3_RPT_TX1: urtwn_r88e_ratectl_tx_complete(sc, &stat[1]); break; default: URTWN_DPRINTF(sc, URTWN_DEBUG_INTR, "%s: case %d was not handled\n", __func__, report_sel); break; } } else return (urtwn_rxeof(sc, buf, len)); return (NULL); } static struct mbuf * urtwn_rxeof(struct urtwn_softc *sc, uint8_t *buf, int len) { struct r92c_rx_stat *stat; struct mbuf *m, *m0 = NULL, *prevm = NULL; uint32_t rxdw0; int totlen, pktlen, infosz, npkts; /* Get the number of encapsulated frames. */ stat = (struct r92c_rx_stat *)buf; npkts = MS(le32toh(stat->rxdw2), R92C_RXDW2_PKTCNT); URTWN_DPRINTF(sc, URTWN_DEBUG_RECV, "%s: Rx %d frames in one chunk\n", __func__, npkts); /* Process all of them. */ while (npkts-- > 0) { if (len < sizeof(*stat)) break; stat = (struct r92c_rx_stat *)buf; rxdw0 = le32toh(stat->rxdw0); pktlen = MS(rxdw0, R92C_RXDW0_PKTLEN); if (pktlen == 0) break; infosz = MS(rxdw0, R92C_RXDW0_INFOSZ) * 8; /* Make sure everything fits in xfer. */ totlen = sizeof(*stat) + infosz + pktlen; if (totlen > len) break; m = urtwn_rx_copy_to_mbuf(sc, stat, totlen); if (m0 == NULL) m0 = m; if (prevm == NULL) prevm = m; else { prevm->m_next = m; prevm = m; } /* Next chunk is 128-byte aligned. */ totlen = (totlen + 127) & ~127; buf += totlen; len -= totlen; } return (m0); } static void urtwn_r88e_ratectl_tx_complete(struct urtwn_softc *sc, void *arg) { struct r88e_tx_rpt_ccx *rpt = arg; struct ieee80211vap *vap; struct ieee80211_node *ni; uint8_t macid; int ntries; macid = MS(rpt->rptb1, R88E_RPTB1_MACID); ntries = MS(rpt->rptb2, R88E_RPTB2_RETRY_CNT); URTWN_NT_LOCK(sc); ni = sc->node_list[macid]; if (ni != NULL) { vap = ni->ni_vap; URTWN_DPRINTF(sc, URTWN_DEBUG_INTR, "%s: frame for macid %d was" "%s sent (%d retries)\n", __func__, macid, (rpt->rptb1 & R88E_RPTB1_PKT_OK) ? "" : " not", ntries); if (rpt->rptb1 & R88E_RPTB1_PKT_OK) { ieee80211_ratectl_tx_complete(vap, ni, IEEE80211_RATECTL_TX_SUCCESS, &ntries, NULL); } else { ieee80211_ratectl_tx_complete(vap, ni, IEEE80211_RATECTL_TX_FAILURE, &ntries, NULL); } } else { URTWN_DPRINTF(sc, URTWN_DEBUG_INTR, "%s: macid %d, ni is NULL\n", __func__, macid); } URTWN_NT_UNLOCK(sc); } static struct ieee80211_node * urtwn_rx_frame(struct urtwn_softc *sc, struct mbuf *m, int8_t *rssi_p) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame_min *wh; struct r92c_rx_stat *stat; uint32_t rxdw0, rxdw3; uint8_t rate, cipher; int8_t rssi = URTWN_NOISE_FLOOR + 1; int infosz; stat = mtod(m, struct r92c_rx_stat *); rxdw0 = le32toh(stat->rxdw0); rxdw3 = le32toh(stat->rxdw3); rate = MS(rxdw3, R92C_RXDW3_RATE); cipher = MS(rxdw0, R92C_RXDW0_CIPHER); infosz = MS(rxdw0, R92C_RXDW0_INFOSZ) * 8; /* Get RSSI from PHY status descriptor if present. */ if (infosz != 0 && (rxdw0 & R92C_RXDW0_PHYST)) { if (sc->chip & URTWN_CHIP_88E) rssi = urtwn_r88e_get_rssi(sc, rate, &stat[1]); else rssi = urtwn_get_rssi(sc, rate, &stat[1]); /* Update our average RSSI. */ urtwn_update_avgrssi(sc, rate, rssi); } if (ieee80211_radiotap_active(ic)) { struct urtwn_rx_radiotap_header *tap = &sc->sc_rxtap; tap->wr_flags = 0; urtwn_get_tsf(sc, &tap->wr_tsft); if (__predict_false(le32toh((uint32_t)tap->wr_tsft) < le32toh(stat->rxdw5))) { tap->wr_tsft = le32toh(tap->wr_tsft >> 32) - 1; tap->wr_tsft = (uint64_t)htole32(tap->wr_tsft) << 32; } else tap->wr_tsft &= 0xffffffff00000000; tap->wr_tsft += stat->rxdw5; /* XXX 20/40? */ /* XXX shortgi? */ /* Map HW rate index to 802.11 rate. */ if (!(rxdw3 & R92C_RXDW3_HT)) { tap->wr_rate = ridx2rate[rate]; } else if (rate >= 12) { /* MCS0~15. */ /* Bit 7 set means HT MCS instead of rate. */ tap->wr_rate = 0x80 | (rate - 12); } tap->wr_dbm_antsignal = rssi; tap->wr_dbm_antnoise = URTWN_NOISE_FLOOR; } *rssi_p = rssi; /* Drop descriptor. */ m_adj(m, sizeof(*stat) + infosz); wh = mtod(m, struct ieee80211_frame_min *); if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) && cipher != R92C_CAM_ALGO_NONE) { m->m_flags |= M_WEP; } if (m->m_len >= sizeof(*wh)) return (ieee80211_find_rxnode(ic, wh)); return (NULL); } static void urtwn_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct urtwn_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni; struct mbuf *m = NULL, *next; struct urtwn_data *data; int8_t nf, rssi; URTWN_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_rx_active); if (data == NULL) goto tr_setup; STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); m = urtwn_report_intr(xfer, data); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: data = STAILQ_FIRST(&sc->sc_rx_inactive); if (data == NULL) { KASSERT(m == NULL, ("mbuf isn't NULL")); - return; + goto finish; } STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next); STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); /* * To avoid LOR we should unlock our private mutex here to call * ieee80211_input() because here is at the end of a USB * callback and safe to unlock. */ while (m != NULL) { next = m->m_next; m->m_next = NULL; ni = urtwn_rx_frame(sc, m, &rssi); URTWN_UNLOCK(sc); nf = URTWN_NOISE_FLOOR; if (ni != NULL) { if (ni->ni_flags & IEEE80211_NODE_HT) m->m_flags |= M_AMPDU; (void)ieee80211_input(ni, m, rssi - nf, nf); ieee80211_free_node(ni); } else { (void)ieee80211_input_all(ic, m, rssi - nf, nf); } - URTWN_LOCK(sc); m = next; } break; default: /* needs it to the inactive queue due to a error. */ data = STAILQ_FIRST(&sc->sc_rx_active); if (data != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); } if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } break; } +finish: + /* Finished receive; age anything left on the FF queue by a little bump */ + /* + * XXX TODO: just make this a callout timer schedule so we can + * flush the FF staging queue if we're approaching idle. + */ +#ifdef IEEE80211_SUPPORT_SUPERG + URTWN_UNLOCK(sc); + ieee80211_ff_age_all(ic, 1); + URTWN_LOCK(sc); +#endif + + /* Kick-start more transmit in case we stalled */ + urtwn_start(sc); } static void urtwn_txeof(struct urtwn_softc *sc, struct urtwn_data *data, int status) { URTWN_ASSERT_LOCKED(sc); if (data->ni != NULL) /* not a beacon frame */ ieee80211_tx_complete(data->ni, data->m, status); + if (sc->sc_tx_n_active > 0) + sc->sc_tx_n_active--; + data->ni = NULL; data->m = NULL; sc->sc_txtimer = 0; STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next); } static int urtwn_alloc_list(struct urtwn_softc *sc, struct urtwn_data data[], int ndata, int maxsz) { int i, error; for (i = 0; i < ndata; i++) { struct urtwn_data *dp = &data[i]; dp->sc = sc; dp->m = NULL; dp->buf = malloc(maxsz, M_USBDEV, M_NOWAIT); if (dp->buf == NULL) { device_printf(sc->sc_dev, "could not allocate buffer\n"); error = ENOMEM; goto fail; } dp->ni = NULL; } return (0); fail: urtwn_free_list(sc, data, ndata); return (error); } static int urtwn_alloc_rx_list(struct urtwn_softc *sc) { int error, i; error = urtwn_alloc_list(sc, sc->sc_rx, URTWN_RX_LIST_COUNT, URTWN_RXBUFSZ); if (error != 0) return (error); STAILQ_INIT(&sc->sc_rx_active); STAILQ_INIT(&sc->sc_rx_inactive); for (i = 0; i < URTWN_RX_LIST_COUNT; i++) STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], next); return (0); } static int urtwn_alloc_tx_list(struct urtwn_softc *sc) { int error, i; error = urtwn_alloc_list(sc, sc->sc_tx, URTWN_TX_LIST_COUNT, URTWN_TXBUFSZ); if (error != 0) return (error); STAILQ_INIT(&sc->sc_tx_active); STAILQ_INIT(&sc->sc_tx_inactive); STAILQ_INIT(&sc->sc_tx_pending); for (i = 0; i < URTWN_TX_LIST_COUNT; i++) STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i], next); return (0); } static void urtwn_free_list(struct urtwn_softc *sc, struct urtwn_data data[], int ndata) { int i; for (i = 0; i < ndata; i++) { struct urtwn_data *dp = &data[i]; if (dp->buf != NULL) { free(dp->buf, M_USBDEV); dp->buf = NULL; } if (dp->ni != NULL) { ieee80211_free_node(dp->ni); dp->ni = NULL; } } } static void urtwn_free_rx_list(struct urtwn_softc *sc) { urtwn_free_list(sc, sc->sc_rx, URTWN_RX_LIST_COUNT); } static void urtwn_free_tx_list(struct urtwn_softc *sc) { urtwn_free_list(sc, sc->sc_tx, URTWN_TX_LIST_COUNT); } static void urtwn_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error) { struct urtwn_softc *sc = usbd_xfer_softc(xfer); +#ifdef IEEE80211_SUPPORT_SUPERG + struct ieee80211com *ic = &sc->sc_ic; +#endif struct urtwn_data *data; URTWN_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)){ case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_tx_active); if (data == NULL) goto tr_setup; STAILQ_REMOVE_HEAD(&sc->sc_tx_active, next); urtwn_txeof(sc, data, 0); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: data = STAILQ_FIRST(&sc->sc_tx_pending); if (data == NULL) { URTWN_DPRINTF(sc, URTWN_DEBUG_XMIT, "%s: empty pending queue\n", __func__); + sc->sc_tx_n_active = 0; goto finish; } STAILQ_REMOVE_HEAD(&sc->sc_tx_pending, next); STAILQ_INSERT_TAIL(&sc->sc_tx_active, data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen); usbd_transfer_submit(xfer); + sc->sc_tx_n_active++; break; default: data = STAILQ_FIRST(&sc->sc_tx_active); if (data == NULL) goto tr_setup; STAILQ_REMOVE_HEAD(&sc->sc_tx_active, next); urtwn_txeof(sc, data, 1); if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } break; } finish: +#ifdef IEEE80211_SUPPORT_SUPERG + /* + * If the TX active queue drops below a certain + * threshold, ensure we age fast-frames out so they're + * transmitted. + */ + if (sc->sc_tx_n_active <= 1) { + /* XXX ew - net80211 should defer this for us! */ + + /* + * Note: this sc_tx_n_active currently tracks + * the number of pending transmit submissions + * and not the actual depth of the TX frames + * pending to the hardware. That means that + * we're going to end up with some sub-optimal + * aggregation behaviour. + */ + /* + * XXX TODO: just make this a callout timer schedule so we can + * flush the FF staging queue if we're approaching idle. + */ + URTWN_UNLOCK(sc); + ieee80211_ff_flush(ic, WME_AC_VO); + ieee80211_ff_flush(ic, WME_AC_VI); + ieee80211_ff_flush(ic, WME_AC_BE); + ieee80211_ff_flush(ic, WME_AC_BK); + URTWN_LOCK(sc); + } +#endif /* Kick-start more transmit */ urtwn_start(sc); } static struct urtwn_data * _urtwn_getbuf(struct urtwn_softc *sc) { struct urtwn_data *bf; bf = STAILQ_FIRST(&sc->sc_tx_inactive); if (bf != NULL) STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next); else { URTWN_DPRINTF(sc, URTWN_DEBUG_XMIT, "%s: out of xmit buffers\n", __func__); } return (bf); } static struct urtwn_data * urtwn_getbuf(struct urtwn_softc *sc) { struct urtwn_data *bf; URTWN_ASSERT_LOCKED(sc); bf = _urtwn_getbuf(sc); if (bf == NULL) { URTWN_DPRINTF(sc, URTWN_DEBUG_XMIT, "%s: stop queue\n", __func__); } return (bf); } static usb_error_t urtwn_write_region_1(struct urtwn_softc *sc, uint16_t addr, uint8_t *buf, int len) { usb_device_request_t req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = R92C_REQ_REGS; USETW(req.wValue, addr); USETW(req.wIndex, 0); USETW(req.wLength, len); return (urtwn_do_request(sc, &req, buf)); } static usb_error_t urtwn_write_1(struct urtwn_softc *sc, uint16_t addr, uint8_t val) { return (urtwn_write_region_1(sc, addr, &val, sizeof(val))); } static usb_error_t urtwn_write_2(struct urtwn_softc *sc, uint16_t addr, uint16_t val) { val = htole16(val); return (urtwn_write_region_1(sc, addr, (uint8_t *)&val, sizeof(val))); } static usb_error_t urtwn_write_4(struct urtwn_softc *sc, uint16_t addr, uint32_t val) { val = htole32(val); return (urtwn_write_region_1(sc, addr, (uint8_t *)&val, sizeof(val))); } static usb_error_t urtwn_read_region_1(struct urtwn_softc *sc, uint16_t addr, uint8_t *buf, int len) { usb_device_request_t req; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = R92C_REQ_REGS; USETW(req.wValue, addr); USETW(req.wIndex, 0); USETW(req.wLength, len); return (urtwn_do_request(sc, &req, buf)); } static uint8_t urtwn_read_1(struct urtwn_softc *sc, uint16_t addr) { uint8_t val; if (urtwn_read_region_1(sc, addr, &val, 1) != 0) return (0xff); return (val); } static uint16_t urtwn_read_2(struct urtwn_softc *sc, uint16_t addr) { uint16_t val; if (urtwn_read_region_1(sc, addr, (uint8_t *)&val, 2) != 0) return (0xffff); return (le16toh(val)); } static uint32_t urtwn_read_4(struct urtwn_softc *sc, uint16_t addr) { uint32_t val; if (urtwn_read_region_1(sc, addr, (uint8_t *)&val, 4) != 0) return (0xffffffff); return (le32toh(val)); } static int urtwn_fw_cmd(struct urtwn_softc *sc, uint8_t id, const void *buf, int len) { struct r92c_fw_cmd cmd; usb_error_t error; int ntries; if (!(sc->sc_flags & URTWN_FW_LOADED)) { URTWN_DPRINTF(sc, URTWN_DEBUG_FIRMWARE, "%s: firmware " "was not loaded; command (id %d) will be discarded\n", __func__, id); return (0); } /* Wait for current FW box to be empty. */ for (ntries = 0; ntries < 100; ntries++) { if (!(urtwn_read_1(sc, R92C_HMETFR) & (1 << sc->fwcur))) break; urtwn_ms_delay(sc); } if (ntries == 100) { device_printf(sc->sc_dev, "could not send firmware command\n"); return (ETIMEDOUT); } memset(&cmd, 0, sizeof(cmd)); cmd.id = id; if (len > 3) cmd.id |= R92C_CMD_FLAG_EXT; KASSERT(len <= sizeof(cmd.msg), ("urtwn_fw_cmd\n")); memcpy(cmd.msg, buf, len); /* Write the first word last since that will trigger the FW. */ error = urtwn_write_region_1(sc, R92C_HMEBOX_EXT(sc->fwcur), (uint8_t *)&cmd + 4, 2); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); error = urtwn_write_region_1(sc, R92C_HMEBOX(sc->fwcur), (uint8_t *)&cmd + 0, 4); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); sc->fwcur = (sc->fwcur + 1) % R92C_H2C_NBOX; return (0); } static void urtwn_cmdq_cb(void *arg, int pending) { struct urtwn_softc *sc = arg; struct urtwn_cmdq *item; /* * Device must be powered on (via urtwn_power_on()) * before any command may be sent. */ URTWN_LOCK(sc); if (!(sc->sc_flags & URTWN_RUNNING)) { URTWN_UNLOCK(sc); return; } URTWN_CMDQ_LOCK(sc); while (sc->cmdq[sc->cmdq_first].func != NULL) { item = &sc->cmdq[sc->cmdq_first]; sc->cmdq_first = (sc->cmdq_first + 1) % URTWN_CMDQ_SIZE; URTWN_CMDQ_UNLOCK(sc); item->func(sc, &item->data); URTWN_CMDQ_LOCK(sc); memset(item, 0, sizeof (*item)); } URTWN_CMDQ_UNLOCK(sc); URTWN_UNLOCK(sc); } static int urtwn_cmd_sleepable(struct urtwn_softc *sc, const void *ptr, size_t len, CMD_FUNC_PROTO) { struct ieee80211com *ic = &sc->sc_ic; KASSERT(len <= sizeof(union sec_param), ("buffer overflow")); URTWN_CMDQ_LOCK(sc); if (sc->cmdq[sc->cmdq_last].func != NULL) { device_printf(sc->sc_dev, "%s: cmdq overflow\n", __func__); URTWN_CMDQ_UNLOCK(sc); return (EAGAIN); } if (ptr != NULL) memcpy(&sc->cmdq[sc->cmdq_last].data, ptr, len); sc->cmdq[sc->cmdq_last].func = func; sc->cmdq_last = (sc->cmdq_last + 1) % URTWN_CMDQ_SIZE; URTWN_CMDQ_UNLOCK(sc); ieee80211_runtask(ic, &sc->cmdq_task); return (0); } static __inline void urtwn_rf_write(struct urtwn_softc *sc, int chain, uint8_t addr, uint32_t val) { sc->sc_rf_write(sc, chain, addr, val); } static void urtwn_r92c_rf_write(struct urtwn_softc *sc, int chain, uint8_t addr, uint32_t val) { urtwn_bb_write(sc, R92C_LSSI_PARAM(chain), SM(R92C_LSSI_PARAM_ADDR, addr) | SM(R92C_LSSI_PARAM_DATA, val)); } static void urtwn_r88e_rf_write(struct urtwn_softc *sc, int chain, uint8_t addr, uint32_t val) { urtwn_bb_write(sc, R92C_LSSI_PARAM(chain), SM(R88E_LSSI_PARAM_ADDR, addr) | SM(R92C_LSSI_PARAM_DATA, val)); } static uint32_t urtwn_rf_read(struct urtwn_softc *sc, int chain, uint8_t addr) { uint32_t reg[R92C_MAX_CHAINS], val; reg[0] = urtwn_bb_read(sc, R92C_HSSI_PARAM2(0)); if (chain != 0) reg[chain] = urtwn_bb_read(sc, R92C_HSSI_PARAM2(chain)); urtwn_bb_write(sc, R92C_HSSI_PARAM2(0), reg[0] & ~R92C_HSSI_PARAM2_READ_EDGE); urtwn_ms_delay(sc); urtwn_bb_write(sc, R92C_HSSI_PARAM2(chain), RW(reg[chain], R92C_HSSI_PARAM2_READ_ADDR, addr) | R92C_HSSI_PARAM2_READ_EDGE); urtwn_ms_delay(sc); urtwn_bb_write(sc, R92C_HSSI_PARAM2(0), reg[0] | R92C_HSSI_PARAM2_READ_EDGE); urtwn_ms_delay(sc); if (urtwn_bb_read(sc, R92C_HSSI_PARAM1(chain)) & R92C_HSSI_PARAM1_PI) val = urtwn_bb_read(sc, R92C_HSPI_READBACK(chain)); else val = urtwn_bb_read(sc, R92C_LSSI_READBACK(chain)); return (MS(val, R92C_LSSI_READBACK_DATA)); } static int urtwn_llt_write(struct urtwn_softc *sc, uint32_t addr, uint32_t data) { usb_error_t error; int ntries; error = urtwn_write_4(sc, R92C_LLT_INIT, SM(R92C_LLT_INIT_OP, R92C_LLT_INIT_OP_WRITE) | SM(R92C_LLT_INIT_ADDR, addr) | SM(R92C_LLT_INIT_DATA, data)); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); /* Wait for write operation to complete. */ for (ntries = 0; ntries < 20; ntries++) { if (MS(urtwn_read_4(sc, R92C_LLT_INIT), R92C_LLT_INIT_OP) == R92C_LLT_INIT_OP_NO_ACTIVE) return (0); urtwn_ms_delay(sc); } return (ETIMEDOUT); } static int urtwn_efuse_read_next(struct urtwn_softc *sc, uint8_t *val) { uint32_t reg; usb_error_t error; int ntries; if (sc->last_rom_addr >= URTWN_EFUSE_MAX_LEN) return (EFAULT); reg = urtwn_read_4(sc, R92C_EFUSE_CTRL); reg = RW(reg, R92C_EFUSE_CTRL_ADDR, sc->last_rom_addr); reg &= ~R92C_EFUSE_CTRL_VALID; error = urtwn_write_4(sc, R92C_EFUSE_CTRL, reg); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); /* Wait for read operation to complete. */ for (ntries = 0; ntries < 100; ntries++) { reg = urtwn_read_4(sc, R92C_EFUSE_CTRL); if (reg & R92C_EFUSE_CTRL_VALID) break; urtwn_ms_delay(sc); } if (ntries == 100) { device_printf(sc->sc_dev, "could not read efuse byte at address 0x%x\n", sc->last_rom_addr); return (ETIMEDOUT); } *val = MS(reg, R92C_EFUSE_CTRL_DATA); sc->last_rom_addr++; return (0); } static int urtwn_efuse_read_data(struct urtwn_softc *sc, uint8_t *rom, uint8_t off, uint8_t msk) { uint8_t reg; int i, error; for (i = 0; i < 4; i++) { if (msk & (1 << i)) continue; error = urtwn_efuse_read_next(sc, ®); if (error != 0) return (error); URTWN_DPRINTF(sc, URTWN_DEBUG_ROM, "rom[0x%03X] == 0x%02X\n", off * 8 + i * 2, reg); rom[off * 8 + i * 2 + 0] = reg; error = urtwn_efuse_read_next(sc, ®); if (error != 0) return (error); URTWN_DPRINTF(sc, URTWN_DEBUG_ROM, "rom[0x%03X] == 0x%02X\n", off * 8 + i * 2 + 1, reg); rom[off * 8 + i * 2 + 1] = reg; } return (0); } #ifdef USB_DEBUG static void urtwn_dump_rom_contents(struct urtwn_softc *sc, uint8_t *rom, uint16_t size) { int i; /* Dump ROM contents. */ device_printf(sc->sc_dev, "%s:", __func__); for (i = 0; i < size; i++) { if (i % 32 == 0) printf("\n%03X: ", i); else if (i % 4 == 0) printf(" "); printf("%02X", rom[i]); } printf("\n"); } #endif static int urtwn_efuse_read(struct urtwn_softc *sc, uint8_t *rom, uint16_t size) { #define URTWN_CHK(res) do { \ if ((error = res) != 0) \ goto end; \ } while(0) uint8_t msk, off, reg; int error; URTWN_CHK(urtwn_efuse_switch_power(sc)); /* Read full ROM image. */ sc->last_rom_addr = 0; memset(rom, 0xff, size); URTWN_CHK(urtwn_efuse_read_next(sc, ®)); while (reg != 0xff) { /* check for extended header */ if ((sc->chip & URTWN_CHIP_88E) && (reg & 0x1f) == 0x0f) { off = reg >> 5; URTWN_CHK(urtwn_efuse_read_next(sc, ®)); if ((reg & 0x0f) != 0x0f) off = ((reg & 0xf0) >> 1) | off; else continue; } else off = reg >> 4; msk = reg & 0xf; URTWN_CHK(urtwn_efuse_read_data(sc, rom, off, msk)); URTWN_CHK(urtwn_efuse_read_next(sc, ®)); } end: #ifdef USB_DEBUG if (sc->sc_debug & URTWN_DEBUG_ROM) urtwn_dump_rom_contents(sc, rom, size); #endif urtwn_write_1(sc, R92C_EFUSE_ACCESS, R92C_EFUSE_ACCESS_OFF); if (error != 0) { device_printf(sc->sc_dev, "%s: error while reading ROM\n", __func__); } return (error); #undef URTWN_CHK } static int urtwn_efuse_switch_power(struct urtwn_softc *sc) { usb_error_t error; uint32_t reg; error = urtwn_write_1(sc, R92C_EFUSE_ACCESS, R92C_EFUSE_ACCESS_ON); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); reg = urtwn_read_2(sc, R92C_SYS_ISO_CTRL); if (!(reg & R92C_SYS_ISO_CTRL_PWC_EV12V)) { error = urtwn_write_2(sc, R92C_SYS_ISO_CTRL, reg | R92C_SYS_ISO_CTRL_PWC_EV12V); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); } reg = urtwn_read_2(sc, R92C_SYS_FUNC_EN); if (!(reg & R92C_SYS_FUNC_EN_ELDR)) { error = urtwn_write_2(sc, R92C_SYS_FUNC_EN, reg | R92C_SYS_FUNC_EN_ELDR); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); } reg = urtwn_read_2(sc, R92C_SYS_CLKR); if ((reg & (R92C_SYS_CLKR_LOADER_EN | R92C_SYS_CLKR_ANA8M)) != (R92C_SYS_CLKR_LOADER_EN | R92C_SYS_CLKR_ANA8M)) { error = urtwn_write_2(sc, R92C_SYS_CLKR, reg | R92C_SYS_CLKR_LOADER_EN | R92C_SYS_CLKR_ANA8M); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); } return (0); } static int urtwn_read_chipid(struct urtwn_softc *sc) { uint32_t reg; if (sc->chip & URTWN_CHIP_88E) return (0); reg = urtwn_read_4(sc, R92C_SYS_CFG); if (reg & R92C_SYS_CFG_TRP_VAUX_EN) return (EIO); if (reg & R92C_SYS_CFG_TYPE_92C) { sc->chip |= URTWN_CHIP_92C; /* Check if it is a castrated 8192C. */ if (MS(urtwn_read_4(sc, R92C_HPON_FSM), R92C_HPON_FSM_CHIP_BONDING_ID) == R92C_HPON_FSM_CHIP_BONDING_ID_92C_1T2R) sc->chip |= URTWN_CHIP_92C_1T2R; } if (reg & R92C_SYS_CFG_VENDOR_UMC) { sc->chip |= URTWN_CHIP_UMC; if (MS(reg, R92C_SYS_CFG_CHIP_VER_RTL) == 0) sc->chip |= URTWN_CHIP_UMC_A_CUT; } return (0); } static int urtwn_read_rom(struct urtwn_softc *sc) { struct r92c_rom *rom = &sc->rom.r92c_rom; int error; /* Read full ROM image. */ error = urtwn_efuse_read(sc, (uint8_t *)rom, sizeof(*rom)); if (error != 0) return (error); /* XXX Weird but this is what the vendor driver does. */ sc->last_rom_addr = 0x1fa; error = urtwn_efuse_read_next(sc, &sc->pa_setting); if (error != 0) return (error); URTWN_DPRINTF(sc, URTWN_DEBUG_ROM, "%s: PA setting=0x%x\n", __func__, sc->pa_setting); sc->board_type = MS(rom->rf_opt1, R92C_ROM_RF1_BOARD_TYPE); sc->regulatory = MS(rom->rf_opt1, R92C_ROM_RF1_REGULATORY); URTWN_DPRINTF(sc, URTWN_DEBUG_ROM, "%s: regulatory type=%d\n", __func__, sc->regulatory); IEEE80211_ADDR_COPY(sc->sc_ic.ic_macaddr, rom->macaddr); sc->sc_rf_write = urtwn_r92c_rf_write; sc->sc_power_on = urtwn_r92c_power_on; sc->sc_power_off = urtwn_r92c_power_off; return (0); } static int urtwn_r88e_read_rom(struct urtwn_softc *sc) { struct r88e_rom *rom = &sc->rom.r88e_rom; int error; error = urtwn_efuse_read(sc, (uint8_t *)rom, sizeof(sc->rom.r88e_rom)); if (error != 0) return (error); sc->bw20_tx_pwr_diff = (rom->tx_pwr_diff >> 4); if (sc->bw20_tx_pwr_diff & 0x08) sc->bw20_tx_pwr_diff |= 0xf0; sc->ofdm_tx_pwr_diff = (rom->tx_pwr_diff & 0xf); if (sc->ofdm_tx_pwr_diff & 0x08) sc->ofdm_tx_pwr_diff |= 0xf0; sc->regulatory = MS(rom->rf_board_opt, R92C_ROM_RF1_REGULATORY); URTWN_DPRINTF(sc, URTWN_DEBUG_ROM, "%s: regulatory type %d\n", __func__,sc->regulatory); IEEE80211_ADDR_COPY(sc->sc_ic.ic_macaddr, rom->macaddr); sc->sc_rf_write = urtwn_r88e_rf_write; sc->sc_power_on = urtwn_r88e_power_on; sc->sc_power_off = urtwn_r88e_power_off; return (0); } /* * Initialize rate adaptation in firmware. */ static int urtwn_ra_init(struct urtwn_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct ieee80211_node *ni; struct ieee80211_rateset *rs, *rs_ht; struct r92c_fw_cmd_macid_cfg cmd; uint32_t rates, basicrates; uint8_t mode; int maxrate, maxbasicrate, error, i, j; ni = ieee80211_ref_node(vap->iv_bss); rs = &ni->ni_rates; rs_ht = (struct ieee80211_rateset *) &ni->ni_htrates; /* Get normal and basic rates mask. */ rates = basicrates = 0; maxrate = maxbasicrate = 0; /* This is for 11bg */ for (i = 0; i < rs->rs_nrates; i++) { /* Convert 802.11 rate to HW rate index. */ for (j = 0; j < nitems(ridx2rate); j++) if ((rs->rs_rates[i] & IEEE80211_RATE_VAL) == ridx2rate[j]) break; if (j == nitems(ridx2rate)) /* Unknown rate, skip. */ continue; rates |= 1 << j; if (j > maxrate) maxrate = j; if (rs->rs_rates[i] & IEEE80211_RATE_BASIC) { basicrates |= 1 << j; if (j > maxbasicrate) maxbasicrate = j; } } /* If we're doing 11n, enable 11n rates */ if (ni->ni_flags & IEEE80211_NODE_HT) { for (i = 0; i < rs_ht->rs_nrates; i++) { if ((rs_ht->rs_rates[i] & 0x7f) > 0xf) continue; /* 11n rates start at index 12 */ j = ((rs_ht->rs_rates[i]) & 0xf) + 12; rates |= (1 << j); /* Guard against the rate table being oddly ordered */ if (j > maxrate) maxrate = j; } } #if 0 if (ic->ic_curmode == IEEE80211_MODE_11NG) raid = R92C_RAID_11GN; #endif /* NB: group addressed frames are done at 11bg rates for now */ if (ic->ic_curmode == IEEE80211_MODE_11B) mode = R92C_RAID_11B; else mode = R92C_RAID_11BG; /* XXX misleading 'mode' value here for unicast frames */ URTWN_DPRINTF(sc, URTWN_DEBUG_RA, "%s: mode 0x%x, rates 0x%08x, basicrates 0x%08x\n", __func__, mode, rates, basicrates); /* Set rates mask for group addressed frames. */ cmd.macid = URTWN_MACID_BC | URTWN_MACID_VALID; cmd.mask = htole32(mode << 28 | basicrates); error = urtwn_fw_cmd(sc, R92C_CMD_MACID_CONFIG, &cmd, sizeof(cmd)); if (error != 0) { ieee80211_free_node(ni); device_printf(sc->sc_dev, "could not add broadcast station\n"); return (error); } /* Set initial MRR rate. */ URTWN_DPRINTF(sc, URTWN_DEBUG_RA, "%s: maxbasicrate %d\n", __func__, maxbasicrate); urtwn_write_1(sc, R92C_INIDATA_RATE_SEL(URTWN_MACID_BC), maxbasicrate); /* Set rates mask for unicast frames. */ if (ni->ni_flags & IEEE80211_NODE_HT) mode = R92C_RAID_11GN; else if (ic->ic_curmode == IEEE80211_MODE_11B) mode = R92C_RAID_11B; else mode = R92C_RAID_11BG; cmd.macid = URTWN_MACID_BSS | URTWN_MACID_VALID; cmd.mask = htole32(mode << 28 | rates); error = urtwn_fw_cmd(sc, R92C_CMD_MACID_CONFIG, &cmd, sizeof(cmd)); if (error != 0) { ieee80211_free_node(ni); device_printf(sc->sc_dev, "could not add BSS station\n"); return (error); } /* Set initial MRR rate. */ URTWN_DPRINTF(sc, URTWN_DEBUG_RA, "%s: maxrate %d\n", __func__, maxrate); urtwn_write_1(sc, R92C_INIDATA_RATE_SEL(URTWN_MACID_BSS), maxrate); /* Indicate highest supported rate. */ if (ni->ni_flags & IEEE80211_NODE_HT) ni->ni_txrate = rs_ht->rs_rates[rs_ht->rs_nrates - 1] | IEEE80211_RATE_MCS; else ni->ni_txrate = rs->rs_rates[rs->rs_nrates - 1]; ieee80211_free_node(ni); return (0); } static void urtwn_init_beacon(struct urtwn_softc *sc, struct urtwn_vap *uvp) { struct r92c_tx_desc *txd = &uvp->bcn_desc; txd->txdw0 = htole32( SM(R92C_TXDW0_OFFSET, sizeof(*txd)) | R92C_TXDW0_BMCAST | R92C_TXDW0_OWN | R92C_TXDW0_FSG | R92C_TXDW0_LSG); txd->txdw1 = htole32( SM(R92C_TXDW1_QSEL, R92C_TXDW1_QSEL_BEACON) | SM(R92C_TXDW1_RAID, R92C_RAID_11B)); if (sc->chip & URTWN_CHIP_88E) { txd->txdw1 |= htole32(SM(R88E_TXDW1_MACID, URTWN_MACID_BC)); txd->txdseq |= htole16(R88E_TXDSEQ_HWSEQ_EN); } else { txd->txdw1 |= htole32(SM(R92C_TXDW1_MACID, URTWN_MACID_BC)); txd->txdw4 |= htole32(R92C_TXDW4_HWSEQ_EN); } txd->txdw4 = htole32(R92C_TXDW4_DRVRATE); txd->txdw5 = htole32(SM(R92C_TXDW5_DATARATE, URTWN_RIDX_CCK1)); } static int urtwn_setup_beacon(struct urtwn_softc *sc, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct urtwn_vap *uvp = URTWN_VAP(vap); struct mbuf *m; int error; URTWN_ASSERT_LOCKED(sc); if (ni->ni_chan == IEEE80211_CHAN_ANYC) return (EINVAL); m = ieee80211_beacon_alloc(ni); if (m == NULL) { device_printf(sc->sc_dev, "%s: could not allocate beacon frame\n", __func__); return (ENOMEM); } if (uvp->bcn_mbuf != NULL) m_freem(uvp->bcn_mbuf); uvp->bcn_mbuf = m; if ((error = urtwn_tx_beacon(sc, uvp)) != 0) return (error); /* XXX bcnq stuck workaround */ if ((error = urtwn_tx_beacon(sc, uvp)) != 0) return (error); URTWN_DPRINTF(sc, URTWN_DEBUG_BEACON, "%s: beacon was %srecognized\n", __func__, urtwn_read_1(sc, R92C_TDECTRL + 2) & (R92C_TDECTRL_BCN_VALID >> 16) ? "" : "not "); return (0); } static void urtwn_update_beacon(struct ieee80211vap *vap, int item) { struct urtwn_softc *sc = vap->iv_ic->ic_softc; struct urtwn_vap *uvp = URTWN_VAP(vap); struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off; struct ieee80211_node *ni = vap->iv_bss; int mcast = 0; URTWN_LOCK(sc); if (uvp->bcn_mbuf == NULL) { uvp->bcn_mbuf = ieee80211_beacon_alloc(ni); if (uvp->bcn_mbuf == NULL) { device_printf(sc->sc_dev, "%s: could not allocate beacon frame\n", __func__); URTWN_UNLOCK(sc); return; } } URTWN_UNLOCK(sc); if (item == IEEE80211_BEACON_TIM) mcast = 1; /* XXX */ setbit(bo->bo_flags, item); ieee80211_beacon_update(ni, uvp->bcn_mbuf, mcast); URTWN_LOCK(sc); urtwn_tx_beacon(sc, uvp); URTWN_UNLOCK(sc); } /* * Push a beacon frame into the chip. Beacon will * be repeated by the chip every R92C_BCN_INTERVAL. */ static int urtwn_tx_beacon(struct urtwn_softc *sc, struct urtwn_vap *uvp) { struct r92c_tx_desc *desc = &uvp->bcn_desc; struct urtwn_data *bf; URTWN_ASSERT_LOCKED(sc); bf = urtwn_getbuf(sc); if (bf == NULL) return (ENOMEM); memcpy(bf->buf, desc, sizeof(*desc)); urtwn_tx_start(sc, uvp->bcn_mbuf, IEEE80211_FC0_TYPE_MGT, bf); sc->sc_txtimer = 5; callout_reset(&sc->sc_watchdog_ch, hz, urtwn_watchdog, sc); return (0); } static int urtwn_key_alloc(struct ieee80211vap *vap, struct ieee80211_key *k, ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix) { struct urtwn_softc *sc = vap->iv_ic->ic_softc; uint8_t i; if (!(&vap->iv_nw_keys[0] <= k && k < &vap->iv_nw_keys[IEEE80211_WEP_NKID])) { if (!(k->wk_flags & IEEE80211_KEY_SWCRYPT)) { URTWN_LOCK(sc); /* * First 4 slots for group keys, * what is left - for pairwise. * XXX incompatible with IBSS RSN. */ for (i = IEEE80211_WEP_NKID; i < R92C_CAM_ENTRY_COUNT; i++) { if ((sc->keys_bmap & (1 << i)) == 0) { sc->keys_bmap |= 1 << i; *keyix = i; break; } } URTWN_UNLOCK(sc); if (i == R92C_CAM_ENTRY_COUNT) { device_printf(sc->sc_dev, "%s: no free space in the key table\n", __func__); return 0; } } else *keyix = 0; } else { *keyix = k - vap->iv_nw_keys; } *rxkeyix = *keyix; return 1; } static void urtwn_key_set_cb(struct urtwn_softc *sc, union sec_param *data) { struct ieee80211_key *k = &data->key; uint8_t algo, keyid; int i, error; if (k->wk_keyix < IEEE80211_WEP_NKID) keyid = k->wk_keyix; else keyid = 0; /* Map net80211 cipher to HW crypto algorithm. */ switch (k->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_WEP: if (k->wk_keylen < 8) algo = R92C_CAM_ALGO_WEP40; else algo = R92C_CAM_ALGO_WEP104; break; case IEEE80211_CIPHER_TKIP: algo = R92C_CAM_ALGO_TKIP; break; case IEEE80211_CIPHER_AES_CCM: algo = R92C_CAM_ALGO_AES; break; default: device_printf(sc->sc_dev, "%s: undefined cipher %d\n", __func__, k->wk_cipher->ic_cipher); return; } URTWN_DPRINTF(sc, URTWN_DEBUG_KEY, "%s: keyix %d, keyid %d, algo %d/%d, flags %04X, len %d, " "macaddr %s\n", __func__, k->wk_keyix, keyid, k->wk_cipher->ic_cipher, algo, k->wk_flags, k->wk_keylen, ether_sprintf(k->wk_macaddr)); /* Write key. */ for (i = 0; i < 4; i++) { error = urtwn_cam_write(sc, R92C_CAM_KEY(k->wk_keyix, i), LE_READ_4(&k->wk_key[i * 4])); if (error != 0) goto fail; } /* Write CTL0 last since that will validate the CAM entry. */ error = urtwn_cam_write(sc, R92C_CAM_CTL1(k->wk_keyix), LE_READ_4(&k->wk_macaddr[2])); if (error != 0) goto fail; error = urtwn_cam_write(sc, R92C_CAM_CTL0(k->wk_keyix), SM(R92C_CAM_ALGO, algo) | SM(R92C_CAM_KEYID, keyid) | SM(R92C_CAM_MACLO, LE_READ_2(&k->wk_macaddr[0])) | R92C_CAM_VALID); if (error != 0) goto fail; return; fail: device_printf(sc->sc_dev, "%s fails, error %d\n", __func__, error); } static void urtwn_key_del_cb(struct urtwn_softc *sc, union sec_param *data) { struct ieee80211_key *k = &data->key; int i; URTWN_DPRINTF(sc, URTWN_DEBUG_KEY, "%s: keyix %d, flags %04X, macaddr %s\n", __func__, k->wk_keyix, k->wk_flags, ether_sprintf(k->wk_macaddr)); urtwn_cam_write(sc, R92C_CAM_CTL0(k->wk_keyix), 0); urtwn_cam_write(sc, R92C_CAM_CTL1(k->wk_keyix), 0); /* Clear key. */ for (i = 0; i < 4; i++) urtwn_cam_write(sc, R92C_CAM_KEY(k->wk_keyix, i), 0); sc->keys_bmap &= ~(1 << k->wk_keyix); } static int urtwn_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k) { struct urtwn_softc *sc = vap->iv_ic->ic_softc; if (k->wk_flags & IEEE80211_KEY_SWCRYPT) { /* Not for us. */ return (1); } return (!urtwn_cmd_sleepable(sc, k, sizeof(*k), urtwn_key_set_cb)); } static int urtwn_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k) { struct urtwn_softc *sc = vap->iv_ic->ic_softc; if (k->wk_flags & IEEE80211_KEY_SWCRYPT) { /* Not for us. */ return (1); } return (!urtwn_cmd_sleepable(sc, k, sizeof(*k), urtwn_key_del_cb)); } static void urtwn_tsf_task_adhoc(void *arg, int pending) { struct ieee80211vap *vap = arg; struct urtwn_softc *sc = vap->iv_ic->ic_softc; struct ieee80211_node *ni; uint32_t reg; URTWN_LOCK(sc); ni = ieee80211_ref_node(vap->iv_bss); reg = urtwn_read_1(sc, R92C_BCN_CTRL); /* Accept beacons with the same BSSID. */ urtwn_set_rx_bssid_all(sc, 0); /* Enable synchronization. */ reg &= ~R92C_BCN_CTRL_DIS_TSF_UDT0; urtwn_write_1(sc, R92C_BCN_CTRL, reg); /* Synchronize. */ usb_pause_mtx(&sc->sc_mtx, hz * ni->ni_intval * 5 / 1000); /* Disable synchronization. */ reg |= R92C_BCN_CTRL_DIS_TSF_UDT0; urtwn_write_1(sc, R92C_BCN_CTRL, reg); /* Remove beacon filter. */ urtwn_set_rx_bssid_all(sc, 1); /* Enable beaconing. */ urtwn_write_1(sc, R92C_MBID_NUM, urtwn_read_1(sc, R92C_MBID_NUM) | R92C_MBID_TXBCN_RPT0); reg |= R92C_BCN_CTRL_EN_BCN; urtwn_write_1(sc, R92C_BCN_CTRL, reg); ieee80211_free_node(ni); URTWN_UNLOCK(sc); } static void urtwn_tsf_sync_enable(struct urtwn_softc *sc, struct ieee80211vap *vap) { struct ieee80211com *ic = &sc->sc_ic; struct urtwn_vap *uvp = URTWN_VAP(vap); /* Reset TSF. */ urtwn_write_1(sc, R92C_DUAL_TSF_RST, R92C_DUAL_TSF_RST0); switch (vap->iv_opmode) { case IEEE80211_M_STA: /* Enable TSF synchronization. */ urtwn_write_1(sc, R92C_BCN_CTRL, urtwn_read_1(sc, R92C_BCN_CTRL) & ~R92C_BCN_CTRL_DIS_TSF_UDT0); break; case IEEE80211_M_IBSS: ieee80211_runtask(ic, &uvp->tsf_task_adhoc); break; case IEEE80211_M_HOSTAP: /* Enable beaconing. */ urtwn_write_1(sc, R92C_MBID_NUM, urtwn_read_1(sc, R92C_MBID_NUM) | R92C_MBID_TXBCN_RPT0); urtwn_write_1(sc, R92C_BCN_CTRL, urtwn_read_1(sc, R92C_BCN_CTRL) | R92C_BCN_CTRL_EN_BCN); break; default: device_printf(sc->sc_dev, "undefined opmode %d\n", vap->iv_opmode); return; } } static void urtwn_get_tsf(struct urtwn_softc *sc, uint64_t *buf) { urtwn_read_region_1(sc, R92C_TSFTR, (uint8_t *)buf, sizeof(*buf)); } static void urtwn_set_led(struct urtwn_softc *sc, int led, int on) { uint8_t reg; if (led == URTWN_LED_LINK) { if (sc->chip & URTWN_CHIP_88E) { reg = urtwn_read_1(sc, R92C_LEDCFG2) & 0xf0; urtwn_write_1(sc, R92C_LEDCFG2, reg | 0x60); if (!on) { reg = urtwn_read_1(sc, R92C_LEDCFG2) & 0x90; urtwn_write_1(sc, R92C_LEDCFG2, reg | R92C_LEDCFG0_DIS); urtwn_write_1(sc, R92C_MAC_PINMUX_CFG, urtwn_read_1(sc, R92C_MAC_PINMUX_CFG) & 0xfe); } } else { reg = urtwn_read_1(sc, R92C_LEDCFG0) & 0x70; if (!on) reg |= R92C_LEDCFG0_DIS; urtwn_write_1(sc, R92C_LEDCFG0, reg); } sc->ledlink = on; /* Save LED state. */ } } static void urtwn_set_mode(struct urtwn_softc *sc, uint8_t mode) { uint8_t reg; reg = urtwn_read_1(sc, R92C_MSR); reg = (reg & ~R92C_MSR_MASK) | mode; urtwn_write_1(sc, R92C_MSR, reg); } static void urtwn_ibss_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { struct ieee80211vap *vap = ni->ni_vap; struct urtwn_softc *sc = vap->iv_ic->ic_softc; struct urtwn_vap *uvp = URTWN_VAP(vap); uint64_t ni_tstamp, curr_tstamp; uvp->recv_mgmt(ni, m, subtype, rxs, rssi, nf); if (vap->iv_state == IEEE80211_S_RUN && (subtype == IEEE80211_FC0_SUBTYPE_BEACON || subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP)) { ni_tstamp = le64toh(ni->ni_tstamp.tsf); URTWN_LOCK(sc); urtwn_get_tsf(sc, &curr_tstamp); URTWN_UNLOCK(sc); curr_tstamp = le64toh(curr_tstamp); if (ni_tstamp >= curr_tstamp) (void) ieee80211_ibss_merge(ni); } } static int urtwn_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct urtwn_vap *uvp = URTWN_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct urtwn_softc *sc = ic->ic_softc; struct ieee80211_node *ni; enum ieee80211_state ostate; uint32_t reg; uint8_t mode; int error = 0; ostate = vap->iv_state; URTWN_DPRINTF(sc, URTWN_DEBUG_STATE, "%s -> %s\n", ieee80211_state_name[ostate], ieee80211_state_name[nstate]); IEEE80211_UNLOCK(ic); URTWN_LOCK(sc); callout_stop(&sc->sc_watchdog_ch); if (ostate == IEEE80211_S_RUN) { /* Stop calibration. */ callout_stop(&sc->sc_calib_to); /* Turn link LED off. */ urtwn_set_led(sc, URTWN_LED_LINK, 0); /* Set media status to 'No Link'. */ urtwn_set_mode(sc, R92C_MSR_NOLINK); /* Stop Rx of data frames. */ urtwn_write_2(sc, R92C_RXFLTMAP2, 0); /* Disable TSF synchronization. */ urtwn_write_1(sc, R92C_BCN_CTRL, (urtwn_read_1(sc, R92C_BCN_CTRL) & ~R92C_BCN_CTRL_EN_BCN) | R92C_BCN_CTRL_DIS_TSF_UDT0); /* Disable beaconing. */ urtwn_write_1(sc, R92C_MBID_NUM, urtwn_read_1(sc, R92C_MBID_NUM) & ~R92C_MBID_TXBCN_RPT0); /* Reset TSF. */ urtwn_write_1(sc, R92C_DUAL_TSF_RST, R92C_DUAL_TSF_RST0); /* Reset EDCA parameters. */ urtwn_write_4(sc, R92C_EDCA_VO_PARAM, 0x002f3217); urtwn_write_4(sc, R92C_EDCA_VI_PARAM, 0x005e4317); urtwn_write_4(sc, R92C_EDCA_BE_PARAM, 0x00105320); urtwn_write_4(sc, R92C_EDCA_BK_PARAM, 0x0000a444); } switch (nstate) { case IEEE80211_S_INIT: /* Turn link LED off. */ urtwn_set_led(sc, URTWN_LED_LINK, 0); break; case IEEE80211_S_SCAN: /* Pause AC Tx queues. */ urtwn_write_1(sc, R92C_TXPAUSE, urtwn_read_1(sc, R92C_TXPAUSE) | R92C_TX_QUEUE_AC); break; case IEEE80211_S_AUTH: urtwn_set_chan(sc, ic->ic_curchan, NULL); break; case IEEE80211_S_RUN: if (vap->iv_opmode == IEEE80211_M_MONITOR) { /* Turn link LED on. */ urtwn_set_led(sc, URTWN_LED_LINK, 1); break; } ni = ieee80211_ref_node(vap->iv_bss); if (ic->ic_bsschan == IEEE80211_CHAN_ANYC || ni->ni_chan == IEEE80211_CHAN_ANYC) { device_printf(sc->sc_dev, "%s: could not move to RUN state\n", __func__); error = EINVAL; goto end_run; } switch (vap->iv_opmode) { case IEEE80211_M_STA: mode = R92C_MSR_INFRA; break; case IEEE80211_M_IBSS: mode = R92C_MSR_ADHOC; break; case IEEE80211_M_HOSTAP: mode = R92C_MSR_AP; break; default: device_printf(sc->sc_dev, "undefined opmode %d\n", vap->iv_opmode); error = EINVAL; goto end_run; } /* Set media status to 'Associated'. */ urtwn_set_mode(sc, mode); /* Set BSSID. */ urtwn_write_4(sc, R92C_BSSID + 0, LE_READ_4(&ni->ni_bssid[0])); urtwn_write_4(sc, R92C_BSSID + 4, LE_READ_2(&ni->ni_bssid[4])); if (ic->ic_curmode == IEEE80211_MODE_11B) urtwn_write_1(sc, R92C_INIRTS_RATE_SEL, 0); else /* 802.11b/g */ urtwn_write_1(sc, R92C_INIRTS_RATE_SEL, 3); /* Enable Rx of data frames. */ urtwn_write_2(sc, R92C_RXFLTMAP2, 0xffff); /* Flush all AC queues. */ urtwn_write_1(sc, R92C_TXPAUSE, 0); /* Set beacon interval. */ urtwn_write_2(sc, R92C_BCN_INTERVAL, ni->ni_intval); /* Allow Rx from our BSSID only. */ if (ic->ic_promisc == 0) { reg = urtwn_read_4(sc, R92C_RCR); if (vap->iv_opmode != IEEE80211_M_HOSTAP) reg |= R92C_RCR_CBSSID_DATA; if (vap->iv_opmode != IEEE80211_M_IBSS) reg |= R92C_RCR_CBSSID_BCN; urtwn_write_4(sc, R92C_RCR, reg); } if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_IBSS) { error = urtwn_setup_beacon(sc, ni); if (error != 0) { device_printf(sc->sc_dev, "unable to push beacon into the chip, " "error %d\n", error); goto end_run; } } /* Enable TSF synchronization. */ urtwn_tsf_sync_enable(sc, vap); urtwn_write_1(sc, R92C_SIFS_CCK + 1, 10); urtwn_write_1(sc, R92C_SIFS_OFDM + 1, 10); urtwn_write_1(sc, R92C_SPEC_SIFS + 1, 10); urtwn_write_1(sc, R92C_MAC_SPEC_SIFS + 1, 10); urtwn_write_1(sc, R92C_R2T_SIFS + 1, 10); urtwn_write_1(sc, R92C_T2T_SIFS + 1, 10); /* Intialize rate adaptation. */ if (!(sc->chip & URTWN_CHIP_88E)) urtwn_ra_init(sc); /* Turn link LED on. */ urtwn_set_led(sc, URTWN_LED_LINK, 1); sc->avg_pwdb = -1; /* Reset average RSSI. */ /* Reset temperature calibration state machine. */ sc->sc_flags &= ~URTWN_TEMP_MEASURED; sc->thcal_lctemp = 0; /* Start periodic calibration. */ callout_reset(&sc->sc_calib_to, 2*hz, urtwn_calib_to, sc); end_run: ieee80211_free_node(ni); break; default: break; } URTWN_UNLOCK(sc); IEEE80211_LOCK(ic); return (error != 0 ? error : uvp->newstate(vap, nstate, arg)); } static void urtwn_calib_to(void *arg) { struct urtwn_softc *sc = arg; /* Do it in a process context. */ urtwn_cmd_sleepable(sc, NULL, 0, urtwn_calib_cb); } static void urtwn_calib_cb(struct urtwn_softc *sc, union sec_param *data) { /* Do temperature compensation. */ urtwn_temp_calib(sc); if ((urtwn_read_1(sc, R92C_MSR) & R92C_MSR_MASK) != R92C_MSR_NOLINK) callout_reset(&sc->sc_calib_to, 2*hz, urtwn_calib_to, sc); } static void urtwn_watchdog(void *arg) { struct urtwn_softc *sc = arg; if (sc->sc_txtimer > 0) { if (--sc->sc_txtimer == 0) { device_printf(sc->sc_dev, "device timeout\n"); counter_u64_add(sc->sc_ic.ic_oerrors, 1); return; } callout_reset(&sc->sc_watchdog_ch, hz, urtwn_watchdog, sc); } } static void urtwn_update_avgrssi(struct urtwn_softc *sc, int rate, int8_t rssi) { int pwdb; /* Convert antenna signal to percentage. */ if (rssi <= -100 || rssi >= 20) pwdb = 0; else if (rssi >= 0) pwdb = 100; else pwdb = 100 + rssi; if (!(sc->chip & URTWN_CHIP_88E)) { if (rate <= URTWN_RIDX_CCK11) { /* CCK gain is smaller than OFDM/MCS gain. */ pwdb += 6; if (pwdb > 100) pwdb = 100; if (pwdb <= 14) pwdb -= 4; else if (pwdb <= 26) pwdb -= 8; else if (pwdb <= 34) pwdb -= 6; else if (pwdb <= 42) pwdb -= 2; } } if (sc->avg_pwdb == -1) /* Init. */ sc->avg_pwdb = pwdb; else if (sc->avg_pwdb < pwdb) sc->avg_pwdb = ((sc->avg_pwdb * 19 + pwdb) / 20) + 1; else sc->avg_pwdb = ((sc->avg_pwdb * 19 + pwdb) / 20); URTWN_DPRINTF(sc, URTWN_DEBUG_RSSI, "%s: PWDB %d, EMA %d\n", __func__, pwdb, sc->avg_pwdb); } static int8_t urtwn_get_rssi(struct urtwn_softc *sc, int rate, void *physt) { static const int8_t cckoff[] = { 16, -12, -26, -46 }; struct r92c_rx_phystat *phy; struct r92c_rx_cck *cck; uint8_t rpt; int8_t rssi; if (rate <= URTWN_RIDX_CCK11) { cck = (struct r92c_rx_cck *)physt; if (sc->sc_flags & URTWN_FLAG_CCK_HIPWR) { rpt = (cck->agc_rpt >> 5) & 0x3; rssi = (cck->agc_rpt & 0x1f) << 1; } else { rpt = (cck->agc_rpt >> 6) & 0x3; rssi = cck->agc_rpt & 0x3e; } rssi = cckoff[rpt] - rssi; } else { /* OFDM/HT. */ phy = (struct r92c_rx_phystat *)physt; rssi = ((le32toh(phy->phydw1) >> 1) & 0x7f) - 110; } return (rssi); } static int8_t urtwn_r88e_get_rssi(struct urtwn_softc *sc, int rate, void *physt) { struct r92c_rx_phystat *phy; struct r88e_rx_cck *cck; uint8_t cck_agc_rpt, lna_idx, vga_idx; int8_t rssi; rssi = 0; if (rate <= URTWN_RIDX_CCK11) { cck = (struct r88e_rx_cck *)physt; cck_agc_rpt = cck->agc_rpt; lna_idx = (cck_agc_rpt & 0xe0) >> 5; vga_idx = cck_agc_rpt & 0x1f; switch (lna_idx) { case 7: if (vga_idx <= 27) rssi = -100 + 2* (27 - vga_idx); else rssi = -100; break; case 6: rssi = -48 + 2 * (2 - vga_idx); break; case 5: rssi = -42 + 2 * (7 - vga_idx); break; case 4: rssi = -36 + 2 * (7 - vga_idx); break; case 3: rssi = -24 + 2 * (7 - vga_idx); break; case 2: rssi = -12 + 2 * (5 - vga_idx); break; case 1: rssi = 8 - (2 * vga_idx); break; case 0: rssi = 14 - (2 * vga_idx); break; } rssi += 6; } else { /* OFDM/HT. */ phy = (struct r92c_rx_phystat *)physt; rssi = ((le32toh(phy->phydw1) >> 1) & 0x7f) - 110; } return (rssi); } static __inline uint8_t rate2ridx(uint8_t rate) { if (rate & IEEE80211_RATE_MCS) { /* 11n rates start at idx 12 */ return ((rate & 0xf) + 12); } switch (rate) { /* 11g */ case 12: return 4; case 18: return 5; case 24: return 6; case 36: return 7; case 48: return 8; case 72: return 9; case 96: return 10; case 108: return 11; /* 11b */ case 2: return 0; case 4: return 1; case 11: return 2; case 22: return 3; default: return 0; } } static int urtwn_tx_data(struct urtwn_softc *sc, struct ieee80211_node *ni, struct mbuf *m, struct urtwn_data *data) { const struct ieee80211_txparam *tp; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_key *k = NULL; struct ieee80211_channel *chan; struct ieee80211_frame *wh; struct r92c_tx_desc *txd; uint8_t macid, raid, rate, ridx, subtype, type, tid, qsel; int hasqos, ismcast; URTWN_ASSERT_LOCKED(sc); /* * Software crypto. */ wh = mtod(m, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; hasqos = IEEE80211_QOS_HAS_SEQ(wh); ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); /* Select TX ring for this frame. */ if (hasqos) { tid = ((const struct ieee80211_qosframe *)wh)->i_qos[0]; tid &= IEEE80211_QOS_TID; } else tid = 0; chan = (ni->ni_chan != IEEE80211_CHAN_ANYC) ? ni->ni_chan : ic->ic_curchan; tp = &vap->iv_txparms[ieee80211_chan2mode(chan)]; /* Choose a TX rate index. */ if (type == IEEE80211_FC0_TYPE_MGT) rate = tp->mgmtrate; else if (ismcast) rate = tp->mcastrate; else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) rate = tp->ucastrate; else if (m->m_flags & M_EAPOL) rate = tp->mgmtrate; else { if (URTWN_CHIP_HAS_RATECTL(sc)) { /* XXX pass pktlen */ (void) ieee80211_ratectl_rate(ni, NULL, 0); rate = ni->ni_txrate; } else { /* XXX TODO: drop the default rate for 11b/11g? */ if (ni->ni_flags & IEEE80211_NODE_HT) rate = IEEE80211_RATE_MCS | 0x4; /* MCS4 */ else if (ic->ic_curmode != IEEE80211_MODE_11B) rate = 108; else rate = 22; } } /* * XXX TODO: this should be per-node, for 11b versus 11bg * nodes in hostap mode */ ridx = rate2ridx(rate); if (ni->ni_flags & IEEE80211_NODE_HT) raid = R92C_RAID_11GN; else if (ic->ic_curmode != IEEE80211_MODE_11B) raid = R92C_RAID_11BG; else raid = R92C_RAID_11B; if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m); if (k == NULL) { device_printf(sc->sc_dev, "ieee80211_crypto_encap returns NULL.\n"); return (ENOBUFS); } /* in case packet header moved, reset pointer */ wh = mtod(m, struct ieee80211_frame *); } /* Fill Tx descriptor. */ txd = (struct r92c_tx_desc *)data->buf; memset(txd, 0, sizeof(*txd)); txd->txdw0 |= htole32( SM(R92C_TXDW0_OFFSET, sizeof(*txd)) | R92C_TXDW0_OWN | R92C_TXDW0_FSG | R92C_TXDW0_LSG); if (ismcast) txd->txdw0 |= htole32(R92C_TXDW0_BMCAST); if (!ismcast) { if (sc->chip & URTWN_CHIP_88E) { struct urtwn_node *un = URTWN_NODE(ni); macid = un->id; } else macid = URTWN_MACID_BSS; if (type == IEEE80211_FC0_TYPE_DATA) { qsel = tid % URTWN_MAX_TID; if (sc->chip & URTWN_CHIP_88E) { txd->txdw2 |= htole32( R88E_TXDW2_AGGBK | R88E_TXDW2_CCX_RPT); } else txd->txdw1 |= htole32(R92C_TXDW1_AGGBK); /* protmode, non-HT */ /* XXX TODO: noack frames? */ if ((rate & 0x80) == 0 && (ic->ic_flags & IEEE80211_F_USEPROT)) { switch (ic->ic_protmode) { case IEEE80211_PROT_CTSONLY: txd->txdw4 |= htole32( R92C_TXDW4_CTS2SELF | R92C_TXDW4_HWRTSEN); break; case IEEE80211_PROT_RTSCTS: txd->txdw4 |= htole32( R92C_TXDW4_RTSEN | R92C_TXDW4_HWRTSEN); break; default: break; } } /* protmode, HT */ /* XXX TODO: noack frames? */ if ((rate & 0x80) && (ic->ic_htprotmode == IEEE80211_PROT_RTSCTS)) { txd->txdw4 |= htole32( R92C_TXDW4_RTSEN | R92C_TXDW4_HWRTSEN); } /* XXX TODO: rtsrate is configurable? 24mbit may * be a bit high for RTS rate? */ txd->txdw4 |= htole32(SM(R92C_TXDW4_RTSRATE, URTWN_RIDX_OFDM24)); txd->txdw5 |= htole32(0x0001ff00); } else /* IEEE80211_FC0_TYPE_MGT */ qsel = R92C_TXDW1_QSEL_MGNT; } else { macid = URTWN_MACID_BC; qsel = R92C_TXDW1_QSEL_MGNT; } txd->txdw1 |= htole32( SM(R92C_TXDW1_QSEL, qsel) | SM(R92C_TXDW1_RAID, raid)); /* XXX TODO: 40MHZ flag? */ /* XXX TODO: AMPDU flag? (AGG_ENABLE or AGG_BREAK?) Density shift? */ /* XXX Short preamble? */ /* XXX Short-GI? */ if (sc->chip & URTWN_CHIP_88E) txd->txdw1 |= htole32(SM(R88E_TXDW1_MACID, macid)); else txd->txdw1 |= htole32(SM(R92C_TXDW1_MACID, macid)); txd->txdw5 |= htole32(SM(R92C_TXDW5_DATARATE, ridx)); /* Force this rate if needed. */ if (URTWN_CHIP_HAS_RATECTL(sc) || ismcast || (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) || (m->m_flags & M_EAPOL) || type != IEEE80211_FC0_TYPE_DATA) txd->txdw4 |= htole32(R92C_TXDW4_DRVRATE); if (!hasqos) { /* Use HW sequence numbering for non-QoS frames. */ if (sc->chip & URTWN_CHIP_88E) txd->txdseq = htole16(R88E_TXDSEQ_HWSEQ_EN); else txd->txdw4 |= htole32(R92C_TXDW4_HWSEQ_EN); } else { /* Set sequence number. */ txd->txdseq = htole16(M_SEQNO_GET(m) % IEEE80211_SEQ_RANGE); } if (k != NULL && !(k->wk_flags & IEEE80211_KEY_SWCRYPT)) { uint8_t cipher; switch (k->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_WEP: case IEEE80211_CIPHER_TKIP: cipher = R92C_TXDW1_CIPHER_RC4; break; case IEEE80211_CIPHER_AES_CCM: cipher = R92C_TXDW1_CIPHER_AES; break; default: device_printf(sc->sc_dev, "%s: unknown cipher %d\n", __func__, k->wk_cipher->ic_cipher); return (EINVAL); } txd->txdw1 |= htole32(SM(R92C_TXDW1_CIPHER, cipher)); } if (ieee80211_radiotap_active_vap(vap)) { struct urtwn_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; if (k != NULL) tap->wt_flags |= IEEE80211_RADIOTAP_F_WEP; ieee80211_radiotap_tx(vap, m); } data->ni = ni; urtwn_tx_start(sc, m, type, data); return (0); } static int urtwn_tx_raw(struct urtwn_softc *sc, struct ieee80211_node *ni, struct mbuf *m, struct urtwn_data *data, const struct ieee80211_bpf_params *params) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_key *k = NULL; struct ieee80211_frame *wh; struct r92c_tx_desc *txd; uint8_t cipher, ridx, type; /* Encrypt the frame if need be. */ cipher = R92C_TXDW1_CIPHER_NONE; if (params->ibp_flags & IEEE80211_BPF_CRYPTO) { /* Retrieve key for TX. */ k = ieee80211_crypto_encap(ni, m); if (k == NULL) return (ENOBUFS); if (!(k->wk_flags & IEEE80211_KEY_SWCRYPT)) { switch (k->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_WEP: case IEEE80211_CIPHER_TKIP: cipher = R92C_TXDW1_CIPHER_RC4; break; case IEEE80211_CIPHER_AES_CCM: cipher = R92C_TXDW1_CIPHER_AES; break; default: device_printf(sc->sc_dev, "%s: unknown cipher %d\n", __func__, k->wk_cipher->ic_cipher); return (EINVAL); } } } /* XXX TODO: 11n checks, matching urtwn_tx_data() */ wh = mtod(m, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; /* Fill Tx descriptor. */ txd = (struct r92c_tx_desc *)data->buf; memset(txd, 0, sizeof(*txd)); txd->txdw0 |= htole32( SM(R92C_TXDW0_OFFSET, sizeof(*txd)) | R92C_TXDW0_OWN | R92C_TXDW0_FSG | R92C_TXDW0_LSG); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) txd->txdw0 |= htole32(R92C_TXDW0_BMCAST); if (params->ibp_flags & IEEE80211_BPF_RTS) txd->txdw4 |= htole32(R92C_TXDW4_RTSEN); if (params->ibp_flags & IEEE80211_BPF_CTS) txd->txdw4 |= htole32(R92C_TXDW4_CTS2SELF); if (txd->txdw4 & htole32(R92C_TXDW4_RTSEN | R92C_TXDW4_CTS2SELF)) { txd->txdw4 |= htole32(R92C_TXDW4_HWRTSEN); txd->txdw4 |= htole32(SM(R92C_TXDW4_RTSRATE, URTWN_RIDX_OFDM24)); } if (sc->chip & URTWN_CHIP_88E) txd->txdw1 |= htole32(SM(R88E_TXDW1_MACID, URTWN_MACID_BC)); else txd->txdw1 |= htole32(SM(R92C_TXDW1_MACID, URTWN_MACID_BC)); /* XXX TODO: rate index/config (RAID) for 11n? */ txd->txdw1 |= htole32(SM(R92C_TXDW1_QSEL, R92C_TXDW1_QSEL_MGNT)); txd->txdw1 |= htole32(SM(R92C_TXDW1_CIPHER, cipher)); /* Choose a TX rate index. */ ridx = rate2ridx(params->ibp_rate0); txd->txdw5 |= htole32(SM(R92C_TXDW5_DATARATE, ridx)); txd->txdw5 |= htole32(0x0001ff00); txd->txdw4 |= htole32(R92C_TXDW4_DRVRATE); if (!IEEE80211_QOS_HAS_SEQ(wh)) { /* Use HW sequence numbering for non-QoS frames. */ if (sc->chip & URTWN_CHIP_88E) txd->txdseq = htole16(R88E_TXDSEQ_HWSEQ_EN); else txd->txdw4 |= htole32(R92C_TXDW4_HWSEQ_EN); } else { /* Set sequence number. */ txd->txdseq = htole16(M_SEQNO_GET(m) % IEEE80211_SEQ_RANGE); } if (ieee80211_radiotap_active_vap(vap)) { struct urtwn_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; if (k != NULL) tap->wt_flags |= IEEE80211_RADIOTAP_F_WEP; ieee80211_radiotap_tx(vap, m); } data->ni = ni; urtwn_tx_start(sc, m, type, data); return (0); } static void urtwn_tx_start(struct urtwn_softc *sc, struct mbuf *m, uint8_t type, struct urtwn_data *data) { struct usb_xfer *xfer; struct r92c_tx_desc *txd; uint16_t ac, sum; int i, xferlen; URTWN_ASSERT_LOCKED(sc); ac = M_WME_GETAC(m); switch (type) { case IEEE80211_FC0_TYPE_CTL: case IEEE80211_FC0_TYPE_MGT: xfer = sc->sc_xfer[URTWN_BULK_TX_VO]; break; default: xfer = sc->sc_xfer[wme2queue[ac].qid]; break; } txd = (struct r92c_tx_desc *)data->buf; txd->txdw0 |= htole32(SM(R92C_TXDW0_PKTLEN, m->m_pkthdr.len)); /* Compute Tx descriptor checksum. */ sum = 0; for (i = 0; i < sizeof(*txd) / 2; i++) sum ^= ((uint16_t *)txd)[i]; txd->txdsum = sum; /* NB: already little endian. */ xferlen = sizeof(*txd) + m->m_pkthdr.len; m_copydata(m, 0, m->m_pkthdr.len, (caddr_t)&txd[1]); data->buflen = xferlen; data->m = m; STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next); usbd_transfer_start(xfer); } static int urtwn_transmit(struct ieee80211com *ic, struct mbuf *m) { struct urtwn_softc *sc = ic->ic_softc; int error; URTWN_LOCK(sc); if ((sc->sc_flags & URTWN_RUNNING) == 0) { URTWN_UNLOCK(sc); return (ENXIO); } error = mbufq_enqueue(&sc->sc_snd, m); if (error) { URTWN_UNLOCK(sc); return (error); } urtwn_start(sc); URTWN_UNLOCK(sc); return (0); } static void urtwn_start(struct urtwn_softc *sc) { struct ieee80211_node *ni; struct mbuf *m; struct urtwn_data *bf; URTWN_ASSERT_LOCKED(sc); while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { bf = urtwn_getbuf(sc); if (bf == NULL) { mbufq_prepend(&sc->sc_snd, m); break; } ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; + + URTWN_DPRINTF(sc, URTWN_DEBUG_XMIT, "%s: called; m=%p\n", + __func__, + m); + if (urtwn_tx_data(sc, ni, m, bf) != 0) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); m_freem(m); ieee80211_free_node(ni); break; } sc->sc_txtimer = 5; callout_reset(&sc->sc_watchdog_ch, hz, urtwn_watchdog, sc); } } static void urtwn_parent(struct ieee80211com *ic) { struct urtwn_softc *sc = ic->ic_softc; URTWN_LOCK(sc); if (sc->sc_flags & URTWN_DETACHED) { URTWN_UNLOCK(sc); return; } URTWN_UNLOCK(sc); if (ic->ic_nrunning > 0) { if (urtwn_init(sc) != 0) { struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); if (vap != NULL) ieee80211_stop(vap); } else ieee80211_start_all(ic); } else urtwn_stop(sc); } static __inline int urtwn_power_on(struct urtwn_softc *sc) { return sc->sc_power_on(sc); } static int urtwn_r92c_power_on(struct urtwn_softc *sc) { uint32_t reg; usb_error_t error; int ntries; /* Wait for autoload done bit. */ for (ntries = 0; ntries < 1000; ntries++) { if (urtwn_read_1(sc, R92C_APS_FSMCO) & R92C_APS_FSMCO_PFM_ALDN) break; urtwn_ms_delay(sc); } if (ntries == 1000) { device_printf(sc->sc_dev, "timeout waiting for chip autoload\n"); return (ETIMEDOUT); } /* Unlock ISO/CLK/Power control register. */ error = urtwn_write_1(sc, R92C_RSV_CTRL, 0); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); /* Move SPS into PWM mode. */ error = urtwn_write_1(sc, R92C_SPS0_CTRL, 0x2b); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); urtwn_ms_delay(sc); reg = urtwn_read_1(sc, R92C_LDOV12D_CTRL); if (!(reg & R92C_LDOV12D_CTRL_LDV12_EN)) { error = urtwn_write_1(sc, R92C_LDOV12D_CTRL, reg | R92C_LDOV12D_CTRL_LDV12_EN); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); urtwn_ms_delay(sc); error = urtwn_write_1(sc, R92C_SYS_ISO_CTRL, urtwn_read_1(sc, R92C_SYS_ISO_CTRL) & ~R92C_SYS_ISO_CTRL_MD2PP); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); } /* Auto enable WLAN. */ error = urtwn_write_2(sc, R92C_APS_FSMCO, urtwn_read_2(sc, R92C_APS_FSMCO) | R92C_APS_FSMCO_APFM_ONMAC); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); for (ntries = 0; ntries < 1000; ntries++) { if (!(urtwn_read_2(sc, R92C_APS_FSMCO) & R92C_APS_FSMCO_APFM_ONMAC)) break; urtwn_ms_delay(sc); } if (ntries == 1000) { device_printf(sc->sc_dev, "timeout waiting for MAC auto ON\n"); return (ETIMEDOUT); } /* Enable radio, GPIO and LED functions. */ error = urtwn_write_2(sc, R92C_APS_FSMCO, R92C_APS_FSMCO_AFSM_HSUS | R92C_APS_FSMCO_PDN_EN | R92C_APS_FSMCO_PFM_ALDN); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); /* Release RF digital isolation. */ error = urtwn_write_2(sc, R92C_SYS_ISO_CTRL, urtwn_read_2(sc, R92C_SYS_ISO_CTRL) & ~R92C_SYS_ISO_CTRL_DIOR); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); /* Initialize MAC. */ error = urtwn_write_1(sc, R92C_APSD_CTRL, urtwn_read_1(sc, R92C_APSD_CTRL) & ~R92C_APSD_CTRL_OFF); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); for (ntries = 0; ntries < 200; ntries++) { if (!(urtwn_read_1(sc, R92C_APSD_CTRL) & R92C_APSD_CTRL_OFF_STATUS)) break; urtwn_ms_delay(sc); } if (ntries == 200) { device_printf(sc->sc_dev, "timeout waiting for MAC initialization\n"); return (ETIMEDOUT); } /* Enable MAC DMA/WMAC/SCHEDULE/SEC blocks. */ reg = urtwn_read_2(sc, R92C_CR); reg |= R92C_CR_HCI_TXDMA_EN | R92C_CR_HCI_RXDMA_EN | R92C_CR_TXDMA_EN | R92C_CR_RXDMA_EN | R92C_CR_PROTOCOL_EN | R92C_CR_SCHEDULE_EN | R92C_CR_MACTXEN | R92C_CR_MACRXEN | R92C_CR_ENSEC; error = urtwn_write_2(sc, R92C_CR, reg); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); error = urtwn_write_1(sc, 0xfe10, 0x19); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); return (0); } static int urtwn_r88e_power_on(struct urtwn_softc *sc) { uint32_t reg; usb_error_t error; int ntries; /* Wait for power ready bit. */ for (ntries = 0; ntries < 5000; ntries++) { if (urtwn_read_4(sc, R92C_APS_FSMCO) & R92C_APS_FSMCO_SUS_HOST) break; urtwn_ms_delay(sc); } if (ntries == 5000) { device_printf(sc->sc_dev, "timeout waiting for chip power up\n"); return (ETIMEDOUT); } /* Reset BB. */ error = urtwn_write_1(sc, R92C_SYS_FUNC_EN, urtwn_read_1(sc, R92C_SYS_FUNC_EN) & ~(R92C_SYS_FUNC_EN_BBRSTB | R92C_SYS_FUNC_EN_BB_GLB_RST)); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); error = urtwn_write_1(sc, R92C_AFE_XTAL_CTRL + 2, urtwn_read_1(sc, R92C_AFE_XTAL_CTRL + 2) | 0x80); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); /* Disable HWPDN. */ error = urtwn_write_2(sc, R92C_APS_FSMCO, urtwn_read_2(sc, R92C_APS_FSMCO) & ~R92C_APS_FSMCO_APDM_HPDN); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); /* Disable WL suspend. */ error = urtwn_write_2(sc, R92C_APS_FSMCO, urtwn_read_2(sc, R92C_APS_FSMCO) & ~(R92C_APS_FSMCO_AFSM_HSUS | R92C_APS_FSMCO_AFSM_PCIE)); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); error = urtwn_write_2(sc, R92C_APS_FSMCO, urtwn_read_2(sc, R92C_APS_FSMCO) | R92C_APS_FSMCO_APFM_ONMAC); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); for (ntries = 0; ntries < 5000; ntries++) { if (!(urtwn_read_2(sc, R92C_APS_FSMCO) & R92C_APS_FSMCO_APFM_ONMAC)) break; urtwn_ms_delay(sc); } if (ntries == 5000) return (ETIMEDOUT); /* Enable LDO normal mode. */ error = urtwn_write_1(sc, R92C_LPLDO_CTRL, urtwn_read_1(sc, R92C_LPLDO_CTRL) & ~R92C_LPLDO_CTRL_SLEEP); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); /* Enable MAC DMA/WMAC/SCHEDULE/SEC blocks. */ error = urtwn_write_2(sc, R92C_CR, 0); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); reg = urtwn_read_2(sc, R92C_CR); reg |= R92C_CR_HCI_TXDMA_EN | R92C_CR_HCI_RXDMA_EN | R92C_CR_TXDMA_EN | R92C_CR_RXDMA_EN | R92C_CR_PROTOCOL_EN | R92C_CR_SCHEDULE_EN | R92C_CR_ENSEC | R92C_CR_CALTMR_EN; error = urtwn_write_2(sc, R92C_CR, reg); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); return (0); } static __inline void urtwn_power_off(struct urtwn_softc *sc) { return sc->sc_power_off(sc); } static void urtwn_r92c_power_off(struct urtwn_softc *sc) { uint32_t reg; /* Block all Tx queues. */ urtwn_write_1(sc, R92C_TXPAUSE, R92C_TX_QUEUE_ALL); /* Disable RF */ urtwn_rf_write(sc, 0, 0, 0); urtwn_write_1(sc, R92C_APSD_CTRL, R92C_APSD_CTRL_OFF); /* Reset BB state machine */ urtwn_write_1(sc, R92C_SYS_FUNC_EN, R92C_SYS_FUNC_EN_USBD | R92C_SYS_FUNC_EN_USBA | R92C_SYS_FUNC_EN_BB_GLB_RST); urtwn_write_1(sc, R92C_SYS_FUNC_EN, R92C_SYS_FUNC_EN_USBD | R92C_SYS_FUNC_EN_USBA); /* * Reset digital sequence */ #ifndef URTWN_WITHOUT_UCODE if (urtwn_read_1(sc, R92C_MCUFWDL) & R92C_MCUFWDL_RDY) { /* Reset MCU ready status */ urtwn_write_1(sc, R92C_MCUFWDL, 0); /* If firmware in ram code, do reset */ urtwn_fw_reset(sc); } #endif /* Reset MAC and Enable 8051 */ urtwn_write_1(sc, R92C_SYS_FUNC_EN + 1, (R92C_SYS_FUNC_EN_CPUEN | R92C_SYS_FUNC_EN_ELDR | R92C_SYS_FUNC_EN_HWPDN) >> 8); /* Reset MCU ready status */ urtwn_write_1(sc, R92C_MCUFWDL, 0); /* Disable MAC clock */ urtwn_write_2(sc, R92C_SYS_CLKR, R92C_SYS_CLKR_ANAD16V_EN | R92C_SYS_CLKR_ANA8M | R92C_SYS_CLKR_LOADER_EN | R92C_SYS_CLKR_80M_SSC_DIS | R92C_SYS_CLKR_SYS_EN | R92C_SYS_CLKR_RING_EN | 0x4000); /* Disable AFE PLL */ urtwn_write_1(sc, R92C_AFE_PLL_CTRL, 0x80); /* Gated AFE DIG_CLOCK */ urtwn_write_2(sc, R92C_AFE_XTAL_CTRL, 0x880F); /* Isolated digital to PON */ urtwn_write_1(sc, R92C_SYS_ISO_CTRL, R92C_SYS_ISO_CTRL_MD2PP | R92C_SYS_ISO_CTRL_PA2PCIE | R92C_SYS_ISO_CTRL_PD2CORE | R92C_SYS_ISO_CTRL_IP2MAC | R92C_SYS_ISO_CTRL_DIOP | R92C_SYS_ISO_CTRL_DIOE); /* * Pull GPIO PIN to balance level and LED control */ /* 1. Disable GPIO[7:0] */ urtwn_write_2(sc, R92C_GPIO_IOSEL, 0x0000); reg = urtwn_read_4(sc, R92C_GPIO_PIN_CTRL) & ~0x0000ff00; reg |= ((reg << 8) & 0x0000ff00) | 0x00ff0000; urtwn_write_4(sc, R92C_GPIO_PIN_CTRL, reg); /* Disable GPIO[10:8] */ urtwn_write_1(sc, R92C_MAC_PINMUX_CFG, 0x00); reg = urtwn_read_2(sc, R92C_GPIO_IO_SEL) & ~0x00f0; reg |= (((reg & 0x000f) << 4) | 0x0780); urtwn_write_2(sc, R92C_GPIO_IO_SEL, reg); /* Disable LED0 & 1 */ urtwn_write_2(sc, R92C_LEDCFG0, 0x8080); /* * Reset digital sequence */ /* Disable ELDR clock */ urtwn_write_2(sc, R92C_SYS_CLKR, R92C_SYS_CLKR_ANAD16V_EN | R92C_SYS_CLKR_ANA8M | R92C_SYS_CLKR_LOADER_EN | R92C_SYS_CLKR_80M_SSC_DIS | R92C_SYS_CLKR_SYS_EN | R92C_SYS_CLKR_RING_EN | 0x4000); /* Isolated ELDR to PON */ urtwn_write_1(sc, R92C_SYS_ISO_CTRL + 1, (R92C_SYS_ISO_CTRL_DIOR | R92C_SYS_ISO_CTRL_PWC_EV12V) >> 8); /* * Disable analog sequence */ /* Disable A15 power */ urtwn_write_1(sc, R92C_LDOA15_CTRL, R92C_LDOA15_CTRL_OBUF); /* Disable digital core power */ urtwn_write_1(sc, R92C_LDOV12D_CTRL, urtwn_read_1(sc, R92C_LDOV12D_CTRL) & ~R92C_LDOV12D_CTRL_LDV12_EN); /* Enter PFM mode */ urtwn_write_1(sc, R92C_SPS0_CTRL, 0x23); /* Set USB suspend */ urtwn_write_2(sc, R92C_APS_FSMCO, R92C_APS_FSMCO_APDM_HOST | R92C_APS_FSMCO_AFSM_HSUS | R92C_APS_FSMCO_PFM_ALDN); /* Lock ISO/CLK/Power control register. */ urtwn_write_1(sc, R92C_RSV_CTRL, 0x0E); } static void urtwn_r88e_power_off(struct urtwn_softc *sc) { uint8_t reg; int ntries; /* Disable any kind of TX reports. */ urtwn_write_1(sc, R88E_TX_RPT_CTRL, urtwn_read_1(sc, R88E_TX_RPT_CTRL) & ~(R88E_TX_RPT1_ENA | R88E_TX_RPT2_ENA)); /* Stop Rx. */ urtwn_write_1(sc, R92C_CR, 0); /* Move card to Low Power State. */ /* Block all Tx queues. */ urtwn_write_1(sc, R92C_TXPAUSE, R92C_TX_QUEUE_ALL); for (ntries = 0; ntries < 20; ntries++) { /* Should be zero if no packet is transmitting. */ if (urtwn_read_4(sc, R88E_SCH_TXCMD) == 0) break; urtwn_ms_delay(sc); } if (ntries == 20) { device_printf(sc->sc_dev, "%s: failed to block Tx queues\n", __func__); return; } /* CCK and OFDM are disabled, and clock are gated. */ urtwn_write_1(sc, R92C_SYS_FUNC_EN, urtwn_read_1(sc, R92C_SYS_FUNC_EN) & ~R92C_SYS_FUNC_EN_BBRSTB); urtwn_ms_delay(sc); /* Reset MAC TRX */ urtwn_write_1(sc, R92C_CR, R92C_CR_HCI_TXDMA_EN | R92C_CR_HCI_RXDMA_EN | R92C_CR_TXDMA_EN | R92C_CR_RXDMA_EN | R92C_CR_PROTOCOL_EN | R92C_CR_SCHEDULE_EN); /* check if removed later */ urtwn_write_1(sc, R92C_CR + 1, urtwn_read_1(sc, R92C_CR + 1) & ~(R92C_CR_ENSEC >> 8)); /* Respond TxOK to scheduler */ urtwn_write_1(sc, R92C_DUAL_TSF_RST, urtwn_read_1(sc, R92C_DUAL_TSF_RST) | 0x20); /* If firmware in ram code, do reset. */ #ifndef URTWN_WITHOUT_UCODE if (urtwn_read_1(sc, R92C_MCUFWDL) & R92C_MCUFWDL_RDY) urtwn_r88e_fw_reset(sc); #endif /* Reset MCU ready status. */ urtwn_write_1(sc, R92C_MCUFWDL, 0x00); /* Disable 32k. */ urtwn_write_1(sc, R88E_32K_CTRL, urtwn_read_1(sc, R88E_32K_CTRL) & ~0x01); /* Move card to Disabled state. */ /* Turn off RF. */ urtwn_write_1(sc, R92C_RF_CTRL, 0); /* LDO Sleep mode. */ urtwn_write_1(sc, R92C_LPLDO_CTRL, urtwn_read_1(sc, R92C_LPLDO_CTRL) | R92C_LPLDO_CTRL_SLEEP); /* Turn off MAC by HW state machine */ urtwn_write_1(sc, R92C_APS_FSMCO + 1, urtwn_read_1(sc, R92C_APS_FSMCO + 1) | (R92C_APS_FSMCO_APFM_OFF >> 8)); for (ntries = 0; ntries < 20; ntries++) { /* Wait until it will be disabled. */ if ((urtwn_read_1(sc, R92C_APS_FSMCO + 1) & (R92C_APS_FSMCO_APFM_OFF >> 8)) == 0) break; urtwn_ms_delay(sc); } if (ntries == 20) { device_printf(sc->sc_dev, "%s: could not turn off MAC\n", __func__); return; } /* schmit trigger */ urtwn_write_1(sc, R92C_AFE_XTAL_CTRL + 2, urtwn_read_1(sc, R92C_AFE_XTAL_CTRL + 2) | 0x80); /* Enable WL suspend. */ urtwn_write_1(sc, R92C_APS_FSMCO + 1, (urtwn_read_1(sc, R92C_APS_FSMCO + 1) & ~0x10) | 0x08); /* Enable bandgap mbias in suspend. */ urtwn_write_1(sc, R92C_APS_FSMCO + 3, 0); /* Clear SIC_EN register. */ urtwn_write_1(sc, R92C_GPIO_MUXCFG + 1, urtwn_read_1(sc, R92C_GPIO_MUXCFG + 1) & ~0x10); /* Set USB suspend enable local register */ urtwn_write_1(sc, R92C_USB_SUSPEND, urtwn_read_1(sc, R92C_USB_SUSPEND) | 0x10); /* Reset MCU IO Wrapper. */ reg = urtwn_read_1(sc, R92C_RSV_CTRL + 1); urtwn_write_1(sc, R92C_RSV_CTRL + 1, reg & ~0x08); urtwn_write_1(sc, R92C_RSV_CTRL + 1, reg | 0x08); /* marked as 'For Power Consumption' code. */ urtwn_write_1(sc, R92C_GPIO_OUT, urtwn_read_1(sc, R92C_GPIO_IN)); urtwn_write_1(sc, R92C_GPIO_IOSEL, 0xff); urtwn_write_1(sc, R92C_GPIO_IO_SEL, urtwn_read_1(sc, R92C_GPIO_IO_SEL) << 4); urtwn_write_1(sc, R92C_GPIO_MOD, urtwn_read_1(sc, R92C_GPIO_MOD) | 0x0f); /* Set LNA, TRSW, EX_PA Pin to output mode. */ urtwn_write_4(sc, R88E_BB_PAD_CTRL, 0x00080808); } static int urtwn_llt_init(struct urtwn_softc *sc) { int i, error, page_count, pktbuf_count; page_count = (sc->chip & URTWN_CHIP_88E) ? R88E_TX_PAGE_COUNT : R92C_TX_PAGE_COUNT; pktbuf_count = (sc->chip & URTWN_CHIP_88E) ? R88E_TXPKTBUF_COUNT : R92C_TXPKTBUF_COUNT; /* Reserve pages [0; page_count]. */ for (i = 0; i < page_count; i++) { if ((error = urtwn_llt_write(sc, i, i + 1)) != 0) return (error); } /* NB: 0xff indicates end-of-list. */ if ((error = urtwn_llt_write(sc, i, 0xff)) != 0) return (error); /* * Use pages [page_count + 1; pktbuf_count - 1] * as ring buffer. */ for (++i; i < pktbuf_count - 1; i++) { if ((error = urtwn_llt_write(sc, i, i + 1)) != 0) return (error); } /* Make the last page point to the beginning of the ring buffer. */ error = urtwn_llt_write(sc, i, page_count + 1); return (error); } #ifndef URTWN_WITHOUT_UCODE static void urtwn_fw_reset(struct urtwn_softc *sc) { uint16_t reg; int ntries; /* Tell 8051 to reset itself. */ urtwn_write_1(sc, R92C_HMETFR + 3, 0x20); /* Wait until 8051 resets by itself. */ for (ntries = 0; ntries < 100; ntries++) { reg = urtwn_read_2(sc, R92C_SYS_FUNC_EN); if (!(reg & R92C_SYS_FUNC_EN_CPUEN)) return; urtwn_ms_delay(sc); } /* Force 8051 reset. */ urtwn_write_2(sc, R92C_SYS_FUNC_EN, reg & ~R92C_SYS_FUNC_EN_CPUEN); } static void urtwn_r88e_fw_reset(struct urtwn_softc *sc) { uint16_t reg; reg = urtwn_read_2(sc, R92C_SYS_FUNC_EN); urtwn_write_2(sc, R92C_SYS_FUNC_EN, reg & ~R92C_SYS_FUNC_EN_CPUEN); urtwn_write_2(sc, R92C_SYS_FUNC_EN, reg | R92C_SYS_FUNC_EN_CPUEN); } static int urtwn_fw_loadpage(struct urtwn_softc *sc, int page, const uint8_t *buf, int len) { uint32_t reg; usb_error_t error = USB_ERR_NORMAL_COMPLETION; int off, mlen; reg = urtwn_read_4(sc, R92C_MCUFWDL); reg = RW(reg, R92C_MCUFWDL_PAGE, page); urtwn_write_4(sc, R92C_MCUFWDL, reg); off = R92C_FW_START_ADDR; while (len > 0) { if (len > 196) mlen = 196; else if (len > 4) mlen = 4; else mlen = 1; /* XXX fix this deconst */ error = urtwn_write_region_1(sc, off, __DECONST(uint8_t *, buf), mlen); if (error != USB_ERR_NORMAL_COMPLETION) break; off += mlen; buf += mlen; len -= mlen; } return (error); } static int urtwn_load_firmware(struct urtwn_softc *sc) { const struct firmware *fw; const struct r92c_fw_hdr *hdr; const char *imagename; const u_char *ptr; size_t len; uint32_t reg; int mlen, ntries, page, error; URTWN_UNLOCK(sc); /* Read firmware image from the filesystem. */ if (sc->chip & URTWN_CHIP_88E) imagename = "urtwn-rtl8188eufw"; else if ((sc->chip & (URTWN_CHIP_UMC_A_CUT | URTWN_CHIP_92C)) == URTWN_CHIP_UMC_A_CUT) imagename = "urtwn-rtl8192cfwU"; else imagename = "urtwn-rtl8192cfwT"; fw = firmware_get(imagename); URTWN_LOCK(sc); if (fw == NULL) { device_printf(sc->sc_dev, "failed loadfirmware of file %s\n", imagename); return (ENOENT); } len = fw->datasize; if (len < sizeof(*hdr)) { device_printf(sc->sc_dev, "firmware too short\n"); error = EINVAL; goto fail; } ptr = fw->data; hdr = (const struct r92c_fw_hdr *)ptr; /* Check if there is a valid FW header and skip it. */ if ((le16toh(hdr->signature) >> 4) == 0x88c || (le16toh(hdr->signature) >> 4) == 0x88e || (le16toh(hdr->signature) >> 4) == 0x92c) { URTWN_DPRINTF(sc, URTWN_DEBUG_FIRMWARE, "FW V%d.%d %02d-%02d %02d:%02d\n", le16toh(hdr->version), le16toh(hdr->subversion), hdr->month, hdr->date, hdr->hour, hdr->minute); ptr += sizeof(*hdr); len -= sizeof(*hdr); } if (urtwn_read_1(sc, R92C_MCUFWDL) & R92C_MCUFWDL_RAM_DL_SEL) { if (sc->chip & URTWN_CHIP_88E) urtwn_r88e_fw_reset(sc); else urtwn_fw_reset(sc); urtwn_write_1(sc, R92C_MCUFWDL, 0); } if (!(sc->chip & URTWN_CHIP_88E)) { urtwn_write_2(sc, R92C_SYS_FUNC_EN, urtwn_read_2(sc, R92C_SYS_FUNC_EN) | R92C_SYS_FUNC_EN_CPUEN); } urtwn_write_1(sc, R92C_MCUFWDL, urtwn_read_1(sc, R92C_MCUFWDL) | R92C_MCUFWDL_EN); urtwn_write_1(sc, R92C_MCUFWDL + 2, urtwn_read_1(sc, R92C_MCUFWDL + 2) & ~0x08); /* Reset the FWDL checksum. */ urtwn_write_1(sc, R92C_MCUFWDL, urtwn_read_1(sc, R92C_MCUFWDL) | R92C_MCUFWDL_CHKSUM_RPT); for (page = 0; len > 0; page++) { mlen = min(len, R92C_FW_PAGE_SIZE); error = urtwn_fw_loadpage(sc, page, ptr, mlen); if (error != 0) { device_printf(sc->sc_dev, "could not load firmware page\n"); goto fail; } ptr += mlen; len -= mlen; } urtwn_write_1(sc, R92C_MCUFWDL, urtwn_read_1(sc, R92C_MCUFWDL) & ~R92C_MCUFWDL_EN); urtwn_write_1(sc, R92C_MCUFWDL + 1, 0); /* Wait for checksum report. */ for (ntries = 0; ntries < 1000; ntries++) { if (urtwn_read_4(sc, R92C_MCUFWDL) & R92C_MCUFWDL_CHKSUM_RPT) break; urtwn_ms_delay(sc); } if (ntries == 1000) { device_printf(sc->sc_dev, "timeout waiting for checksum report\n"); error = ETIMEDOUT; goto fail; } reg = urtwn_read_4(sc, R92C_MCUFWDL); reg = (reg & ~R92C_MCUFWDL_WINTINI_RDY) | R92C_MCUFWDL_RDY; urtwn_write_4(sc, R92C_MCUFWDL, reg); if (sc->chip & URTWN_CHIP_88E) urtwn_r88e_fw_reset(sc); /* Wait for firmware readiness. */ for (ntries = 0; ntries < 1000; ntries++) { if (urtwn_read_4(sc, R92C_MCUFWDL) & R92C_MCUFWDL_WINTINI_RDY) break; urtwn_ms_delay(sc); } if (ntries == 1000) { device_printf(sc->sc_dev, "timeout waiting for firmware readiness\n"); error = ETIMEDOUT; goto fail; } fail: firmware_put(fw, FIRMWARE_UNLOAD); return (error); } #endif static int urtwn_dma_init(struct urtwn_softc *sc) { struct usb_endpoint *ep, *ep_end; usb_error_t usb_err; uint32_t reg; int hashq, hasnq, haslq, nqueues, ntx; int error, pagecount, npubqpages, nqpages, nrempages, tx_boundary; /* Initialize LLT table. */ error = urtwn_llt_init(sc); if (error != 0) return (error); /* Determine the number of bulk-out pipes. */ ntx = 0; ep = sc->sc_udev->endpoints; ep_end = sc->sc_udev->endpoints + sc->sc_udev->endpoints_max; for (; ep != ep_end; ep++) { if ((ep->edesc == NULL) || (ep->iface_index != sc->sc_iface_index)) continue; if (UE_GET_DIR(ep->edesc->bEndpointAddress) == UE_DIR_OUT) ntx++; } if (ntx == 0) { device_printf(sc->sc_dev, "%d: invalid number of Tx bulk pipes\n", ntx); return (EIO); } /* Get Tx queues to USB endpoints mapping. */ hashq = hasnq = haslq = nqueues = 0; switch (ntx) { case 1: hashq = 1; break; case 2: hashq = hasnq = 1; break; case 3: case 4: hashq = hasnq = haslq = 1; break; } nqueues = hashq + hasnq + haslq; if (nqueues == 0) return (EIO); npubqpages = nqpages = nrempages = pagecount = 0; if (sc->chip & URTWN_CHIP_88E) tx_boundary = R88E_TX_PAGE_BOUNDARY; else { pagecount = R92C_TX_PAGE_COUNT; npubqpages = R92C_PUBQ_NPAGES; tx_boundary = R92C_TX_PAGE_BOUNDARY; } /* Set number of pages for normal priority queue. */ if (sc->chip & URTWN_CHIP_88E) { usb_err = urtwn_write_2(sc, R92C_RQPN_NPQ, 0xd); if (usb_err != USB_ERR_NORMAL_COMPLETION) return (EIO); usb_err = urtwn_write_4(sc, R92C_RQPN, 0x808e000d); if (usb_err != USB_ERR_NORMAL_COMPLETION) return (EIO); } else { /* Get the number of pages for each queue. */ nqpages = (pagecount - npubqpages) / nqueues; /* * The remaining pages are assigned to the high priority * queue. */ nrempages = (pagecount - npubqpages) % nqueues; usb_err = urtwn_write_1(sc, R92C_RQPN_NPQ, hasnq ? nqpages : 0); if (usb_err != USB_ERR_NORMAL_COMPLETION) return (EIO); usb_err = urtwn_write_4(sc, R92C_RQPN, /* Set number of pages for public queue. */ SM(R92C_RQPN_PUBQ, npubqpages) | /* Set number of pages for high priority queue. */ SM(R92C_RQPN_HPQ, hashq ? nqpages + nrempages : 0) | /* Set number of pages for low priority queue. */ SM(R92C_RQPN_LPQ, haslq ? nqpages : 0) | /* Load values. */ R92C_RQPN_LD); if (usb_err != USB_ERR_NORMAL_COMPLETION) return (EIO); } usb_err = urtwn_write_1(sc, R92C_TXPKTBUF_BCNQ_BDNY, tx_boundary); if (usb_err != USB_ERR_NORMAL_COMPLETION) return (EIO); usb_err = urtwn_write_1(sc, R92C_TXPKTBUF_MGQ_BDNY, tx_boundary); if (usb_err != USB_ERR_NORMAL_COMPLETION) return (EIO); usb_err = urtwn_write_1(sc, R92C_TXPKTBUF_WMAC_LBK_BF_HD, tx_boundary); if (usb_err != USB_ERR_NORMAL_COMPLETION) return (EIO); usb_err = urtwn_write_1(sc, R92C_TRXFF_BNDY, tx_boundary); if (usb_err != USB_ERR_NORMAL_COMPLETION) return (EIO); usb_err = urtwn_write_1(sc, R92C_TDECTRL + 1, tx_boundary); if (usb_err != USB_ERR_NORMAL_COMPLETION) return (EIO); /* Set queue to USB pipe mapping. */ reg = urtwn_read_2(sc, R92C_TRXDMA_CTRL); reg &= ~R92C_TRXDMA_CTRL_QMAP_M; if (nqueues == 1) { if (hashq) reg |= R92C_TRXDMA_CTRL_QMAP_HQ; else if (hasnq) reg |= R92C_TRXDMA_CTRL_QMAP_NQ; else reg |= R92C_TRXDMA_CTRL_QMAP_LQ; } else if (nqueues == 2) { /* * All 2-endpoints configs have high and normal * priority queues. */ reg |= R92C_TRXDMA_CTRL_QMAP_HQ_NQ; } else reg |= R92C_TRXDMA_CTRL_QMAP_3EP; usb_err = urtwn_write_2(sc, R92C_TRXDMA_CTRL, reg); if (usb_err != USB_ERR_NORMAL_COMPLETION) return (EIO); /* Set Tx/Rx transfer page boundary. */ usb_err = urtwn_write_2(sc, R92C_TRXFF_BNDY + 2, (sc->chip & URTWN_CHIP_88E) ? 0x23ff : 0x27ff); if (usb_err != USB_ERR_NORMAL_COMPLETION) return (EIO); /* Set Tx/Rx transfer page size. */ usb_err = urtwn_write_1(sc, R92C_PBP, SM(R92C_PBP_PSRX, R92C_PBP_128) | SM(R92C_PBP_PSTX, R92C_PBP_128)); if (usb_err != USB_ERR_NORMAL_COMPLETION) return (EIO); return (0); } static int urtwn_mac_init(struct urtwn_softc *sc) { usb_error_t error; int i; /* Write MAC initialization values. */ if (sc->chip & URTWN_CHIP_88E) { for (i = 0; i < nitems(rtl8188eu_mac); i++) { error = urtwn_write_1(sc, rtl8188eu_mac[i].reg, rtl8188eu_mac[i].val); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); } urtwn_write_1(sc, R92C_MAX_AGGR_NUM, 0x07); } else { for (i = 0; i < nitems(rtl8192cu_mac); i++) error = urtwn_write_1(sc, rtl8192cu_mac[i].reg, rtl8192cu_mac[i].val); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); } return (0); } static void urtwn_bb_init(struct urtwn_softc *sc) { const struct urtwn_bb_prog *prog; uint32_t reg; uint8_t crystalcap; int i; /* Enable BB and RF. */ urtwn_write_2(sc, R92C_SYS_FUNC_EN, urtwn_read_2(sc, R92C_SYS_FUNC_EN) | R92C_SYS_FUNC_EN_BBRSTB | R92C_SYS_FUNC_EN_BB_GLB_RST | R92C_SYS_FUNC_EN_DIO_RF); if (!(sc->chip & URTWN_CHIP_88E)) urtwn_write_2(sc, R92C_AFE_PLL_CTRL, 0xdb83); urtwn_write_1(sc, R92C_RF_CTRL, R92C_RF_CTRL_EN | R92C_RF_CTRL_RSTB | R92C_RF_CTRL_SDMRSTB); urtwn_write_1(sc, R92C_SYS_FUNC_EN, R92C_SYS_FUNC_EN_USBA | R92C_SYS_FUNC_EN_USBD | R92C_SYS_FUNC_EN_BB_GLB_RST | R92C_SYS_FUNC_EN_BBRSTB); if (!(sc->chip & URTWN_CHIP_88E)) { urtwn_write_1(sc, R92C_LDOHCI12_CTRL, 0x0f); urtwn_write_1(sc, 0x15, 0xe9); urtwn_write_1(sc, R92C_AFE_XTAL_CTRL + 1, 0x80); } /* Select BB programming based on board type. */ if (sc->chip & URTWN_CHIP_88E) prog = &rtl8188eu_bb_prog; else if (!(sc->chip & URTWN_CHIP_92C)) { if (sc->board_type == R92C_BOARD_TYPE_MINICARD) prog = &rtl8188ce_bb_prog; else if (sc->board_type == R92C_BOARD_TYPE_HIGHPA) prog = &rtl8188ru_bb_prog; else prog = &rtl8188cu_bb_prog; } else { if (sc->board_type == R92C_BOARD_TYPE_MINICARD) prog = &rtl8192ce_bb_prog; else prog = &rtl8192cu_bb_prog; } /* Write BB initialization values. */ for (i = 0; i < prog->count; i++) { urtwn_bb_write(sc, prog->regs[i], prog->vals[i]); urtwn_ms_delay(sc); } if (sc->chip & URTWN_CHIP_92C_1T2R) { /* 8192C 1T only configuration. */ reg = urtwn_bb_read(sc, R92C_FPGA0_TXINFO); reg = (reg & ~0x00000003) | 0x2; urtwn_bb_write(sc, R92C_FPGA0_TXINFO, reg); reg = urtwn_bb_read(sc, R92C_FPGA1_TXINFO); reg = (reg & ~0x00300033) | 0x00200022; urtwn_bb_write(sc, R92C_FPGA1_TXINFO, reg); reg = urtwn_bb_read(sc, R92C_CCK0_AFESETTING); reg = (reg & ~0xff000000) | 0x45 << 24; urtwn_bb_write(sc, R92C_CCK0_AFESETTING, reg); reg = urtwn_bb_read(sc, R92C_OFDM0_TRXPATHENA); reg = (reg & ~0x000000ff) | 0x23; urtwn_bb_write(sc, R92C_OFDM0_TRXPATHENA, reg); reg = urtwn_bb_read(sc, R92C_OFDM0_AGCPARAM1); reg = (reg & ~0x00000030) | 1 << 4; urtwn_bb_write(sc, R92C_OFDM0_AGCPARAM1, reg); reg = urtwn_bb_read(sc, 0xe74); reg = (reg & ~0x0c000000) | 2 << 26; urtwn_bb_write(sc, 0xe74, reg); reg = urtwn_bb_read(sc, 0xe78); reg = (reg & ~0x0c000000) | 2 << 26; urtwn_bb_write(sc, 0xe78, reg); reg = urtwn_bb_read(sc, 0xe7c); reg = (reg & ~0x0c000000) | 2 << 26; urtwn_bb_write(sc, 0xe7c, reg); reg = urtwn_bb_read(sc, 0xe80); reg = (reg & ~0x0c000000) | 2 << 26; urtwn_bb_write(sc, 0xe80, reg); reg = urtwn_bb_read(sc, 0xe88); reg = (reg & ~0x0c000000) | 2 << 26; urtwn_bb_write(sc, 0xe88, reg); } /* Write AGC values. */ for (i = 0; i < prog->agccount; i++) { urtwn_bb_write(sc, R92C_OFDM0_AGCRSSITABLE, prog->agcvals[i]); urtwn_ms_delay(sc); } if (sc->chip & URTWN_CHIP_88E) { urtwn_bb_write(sc, R92C_OFDM0_AGCCORE1(0), 0x69553422); urtwn_ms_delay(sc); urtwn_bb_write(sc, R92C_OFDM0_AGCCORE1(0), 0x69553420); urtwn_ms_delay(sc); crystalcap = sc->rom.r88e_rom.crystalcap; if (crystalcap == 0xff) crystalcap = 0x20; crystalcap &= 0x3f; reg = urtwn_bb_read(sc, R92C_AFE_XTAL_CTRL); urtwn_bb_write(sc, R92C_AFE_XTAL_CTRL, RW(reg, R92C_AFE_XTAL_CTRL_ADDR, crystalcap | crystalcap << 6)); } else { if (urtwn_bb_read(sc, R92C_HSSI_PARAM2(0)) & R92C_HSSI_PARAM2_CCK_HIPWR) sc->sc_flags |= URTWN_FLAG_CCK_HIPWR; } } static void urtwn_rf_init(struct urtwn_softc *sc) { const struct urtwn_rf_prog *prog; uint32_t reg, type; int i, j, idx, off; /* Select RF programming based on board type. */ if (sc->chip & URTWN_CHIP_88E) prog = rtl8188eu_rf_prog; else if (!(sc->chip & URTWN_CHIP_92C)) { if (sc->board_type == R92C_BOARD_TYPE_MINICARD) prog = rtl8188ce_rf_prog; else if (sc->board_type == R92C_BOARD_TYPE_HIGHPA) prog = rtl8188ru_rf_prog; else prog = rtl8188cu_rf_prog; } else prog = rtl8192ce_rf_prog; for (i = 0; i < sc->nrxchains; i++) { /* Save RF_ENV control type. */ idx = i / 2; off = (i % 2) * 16; reg = urtwn_bb_read(sc, R92C_FPGA0_RFIFACESW(idx)); type = (reg >> off) & 0x10; /* Set RF_ENV enable. */ reg = urtwn_bb_read(sc, R92C_FPGA0_RFIFACEOE(i)); reg |= 0x100000; urtwn_bb_write(sc, R92C_FPGA0_RFIFACEOE(i), reg); urtwn_ms_delay(sc); /* Set RF_ENV output high. */ reg = urtwn_bb_read(sc, R92C_FPGA0_RFIFACEOE(i)); reg |= 0x10; urtwn_bb_write(sc, R92C_FPGA0_RFIFACEOE(i), reg); urtwn_ms_delay(sc); /* Set address and data lengths of RF registers. */ reg = urtwn_bb_read(sc, R92C_HSSI_PARAM2(i)); reg &= ~R92C_HSSI_PARAM2_ADDR_LENGTH; urtwn_bb_write(sc, R92C_HSSI_PARAM2(i), reg); urtwn_ms_delay(sc); reg = urtwn_bb_read(sc, R92C_HSSI_PARAM2(i)); reg &= ~R92C_HSSI_PARAM2_DATA_LENGTH; urtwn_bb_write(sc, R92C_HSSI_PARAM2(i), reg); urtwn_ms_delay(sc); /* Write RF initialization values for this chain. */ for (j = 0; j < prog[i].count; j++) { if (prog[i].regs[j] >= 0xf9 && prog[i].regs[j] <= 0xfe) { /* * These are fake RF registers offsets that * indicate a delay is required. */ usb_pause_mtx(&sc->sc_mtx, hz / 20); /* 50ms */ continue; } urtwn_rf_write(sc, i, prog[i].regs[j], prog[i].vals[j]); urtwn_ms_delay(sc); } /* Restore RF_ENV control type. */ reg = urtwn_bb_read(sc, R92C_FPGA0_RFIFACESW(idx)); reg &= ~(0x10 << off) | (type << off); urtwn_bb_write(sc, R92C_FPGA0_RFIFACESW(idx), reg); /* Cache RF register CHNLBW. */ sc->rf_chnlbw[i] = urtwn_rf_read(sc, i, R92C_RF_CHNLBW); } if ((sc->chip & (URTWN_CHIP_UMC_A_CUT | URTWN_CHIP_92C)) == URTWN_CHIP_UMC_A_CUT) { urtwn_rf_write(sc, 0, R92C_RF_RX_G1, 0x30255); urtwn_rf_write(sc, 0, R92C_RF_RX_G2, 0x50a00); } } static void urtwn_cam_init(struct urtwn_softc *sc) { /* Invalidate all CAM entries. */ urtwn_write_4(sc, R92C_CAMCMD, R92C_CAMCMD_POLLING | R92C_CAMCMD_CLR); } static int urtwn_cam_write(struct urtwn_softc *sc, uint32_t addr, uint32_t data) { usb_error_t error; error = urtwn_write_4(sc, R92C_CAMWRITE, data); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); error = urtwn_write_4(sc, R92C_CAMCMD, R92C_CAMCMD_POLLING | R92C_CAMCMD_WRITE | SM(R92C_CAMCMD_ADDR, addr)); if (error != USB_ERR_NORMAL_COMPLETION) return (EIO); return (0); } static void urtwn_pa_bias_init(struct urtwn_softc *sc) { uint8_t reg; int i; for (i = 0; i < sc->nrxchains; i++) { if (sc->pa_setting & (1 << i)) continue; urtwn_rf_write(sc, i, R92C_RF_IPA, 0x0f406); urtwn_rf_write(sc, i, R92C_RF_IPA, 0x4f406); urtwn_rf_write(sc, i, R92C_RF_IPA, 0x8f406); urtwn_rf_write(sc, i, R92C_RF_IPA, 0xcf406); } if (!(sc->pa_setting & 0x10)) { reg = urtwn_read_1(sc, 0x16); reg = (reg & ~0xf0) | 0x90; urtwn_write_1(sc, 0x16, reg); } } static void urtwn_rxfilter_init(struct urtwn_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint32_t rcr; uint16_t filter; URTWN_ASSERT_LOCKED(sc); /* Accept all multicast frames. */ urtwn_write_4(sc, R92C_MAR + 0, 0xffffffff); urtwn_write_4(sc, R92C_MAR + 4, 0xffffffff); /* Filter for management frames. */ filter = 0x7f3f; switch (vap->iv_opmode) { case IEEE80211_M_STA: filter &= ~( R92C_RXFLTMAP_SUBTYPE(IEEE80211_FC0_SUBTYPE_ASSOC_REQ) | R92C_RXFLTMAP_SUBTYPE(IEEE80211_FC0_SUBTYPE_REASSOC_REQ) | R92C_RXFLTMAP_SUBTYPE(IEEE80211_FC0_SUBTYPE_PROBE_REQ)); break; case IEEE80211_M_HOSTAP: filter &= ~( R92C_RXFLTMAP_SUBTYPE(IEEE80211_FC0_SUBTYPE_ASSOC_RESP) | R92C_RXFLTMAP_SUBTYPE(IEEE80211_FC0_SUBTYPE_REASSOC_RESP)); break; case IEEE80211_M_MONITOR: case IEEE80211_M_IBSS: break; default: device_printf(sc->sc_dev, "%s: undefined opmode %d\n", __func__, vap->iv_opmode); break; } urtwn_write_2(sc, R92C_RXFLTMAP0, filter); /* Reject all control frames. */ urtwn_write_2(sc, R92C_RXFLTMAP1, 0x0000); /* Reject all data frames. */ urtwn_write_2(sc, R92C_RXFLTMAP2, 0x0000); rcr = R92C_RCR_AM | R92C_RCR_AB | R92C_RCR_APM | R92C_RCR_HTC_LOC_CTRL | R92C_RCR_APP_PHYSTS | R92C_RCR_APP_ICV | R92C_RCR_APP_MIC; if (vap->iv_opmode == IEEE80211_M_MONITOR) { /* Accept all frames. */ rcr |= R92C_RCR_ACF | R92C_RCR_ADF | R92C_RCR_AMF | R92C_RCR_AAP; } /* Set Rx filter. */ urtwn_write_4(sc, R92C_RCR, rcr); if (ic->ic_promisc != 0) { /* Update Rx filter. */ urtwn_set_promisc(sc); } } static void urtwn_edca_init(struct urtwn_softc *sc) { urtwn_write_2(sc, R92C_SPEC_SIFS, 0x100a); urtwn_write_2(sc, R92C_MAC_SPEC_SIFS, 0x100a); urtwn_write_2(sc, R92C_SIFS_CCK, 0x100a); urtwn_write_2(sc, R92C_SIFS_OFDM, 0x100a); urtwn_write_4(sc, R92C_EDCA_BE_PARAM, 0x005ea42b); urtwn_write_4(sc, R92C_EDCA_BK_PARAM, 0x0000a44f); urtwn_write_4(sc, R92C_EDCA_VI_PARAM, 0x005ea324); urtwn_write_4(sc, R92C_EDCA_VO_PARAM, 0x002fa226); } static void urtwn_write_txpower(struct urtwn_softc *sc, int chain, uint16_t power[URTWN_RIDX_COUNT]) { uint32_t reg; /* Write per-CCK rate Tx power. */ if (chain == 0) { reg = urtwn_bb_read(sc, R92C_TXAGC_A_CCK1_MCS32); reg = RW(reg, R92C_TXAGC_A_CCK1, power[0]); urtwn_bb_write(sc, R92C_TXAGC_A_CCK1_MCS32, reg); reg = urtwn_bb_read(sc, R92C_TXAGC_B_CCK11_A_CCK2_11); reg = RW(reg, R92C_TXAGC_A_CCK2, power[1]); reg = RW(reg, R92C_TXAGC_A_CCK55, power[2]); reg = RW(reg, R92C_TXAGC_A_CCK11, power[3]); urtwn_bb_write(sc, R92C_TXAGC_B_CCK11_A_CCK2_11, reg); } else { reg = urtwn_bb_read(sc, R92C_TXAGC_B_CCK1_55_MCS32); reg = RW(reg, R92C_TXAGC_B_CCK1, power[0]); reg = RW(reg, R92C_TXAGC_B_CCK2, power[1]); reg = RW(reg, R92C_TXAGC_B_CCK55, power[2]); urtwn_bb_write(sc, R92C_TXAGC_B_CCK1_55_MCS32, reg); reg = urtwn_bb_read(sc, R92C_TXAGC_B_CCK11_A_CCK2_11); reg = RW(reg, R92C_TXAGC_B_CCK11, power[3]); urtwn_bb_write(sc, R92C_TXAGC_B_CCK11_A_CCK2_11, reg); } /* Write per-OFDM rate Tx power. */ urtwn_bb_write(sc, R92C_TXAGC_RATE18_06(chain), SM(R92C_TXAGC_RATE06, power[ 4]) | SM(R92C_TXAGC_RATE09, power[ 5]) | SM(R92C_TXAGC_RATE12, power[ 6]) | SM(R92C_TXAGC_RATE18, power[ 7])); urtwn_bb_write(sc, R92C_TXAGC_RATE54_24(chain), SM(R92C_TXAGC_RATE24, power[ 8]) | SM(R92C_TXAGC_RATE36, power[ 9]) | SM(R92C_TXAGC_RATE48, power[10]) | SM(R92C_TXAGC_RATE54, power[11])); /* Write per-MCS Tx power. */ urtwn_bb_write(sc, R92C_TXAGC_MCS03_MCS00(chain), SM(R92C_TXAGC_MCS00, power[12]) | SM(R92C_TXAGC_MCS01, power[13]) | SM(R92C_TXAGC_MCS02, power[14]) | SM(R92C_TXAGC_MCS03, power[15])); urtwn_bb_write(sc, R92C_TXAGC_MCS07_MCS04(chain), SM(R92C_TXAGC_MCS04, power[16]) | SM(R92C_TXAGC_MCS05, power[17]) | SM(R92C_TXAGC_MCS06, power[18]) | SM(R92C_TXAGC_MCS07, power[19])); urtwn_bb_write(sc, R92C_TXAGC_MCS11_MCS08(chain), SM(R92C_TXAGC_MCS08, power[20]) | SM(R92C_TXAGC_MCS09, power[21]) | SM(R92C_TXAGC_MCS10, power[22]) | SM(R92C_TXAGC_MCS11, power[23])); urtwn_bb_write(sc, R92C_TXAGC_MCS15_MCS12(chain), SM(R92C_TXAGC_MCS12, power[24]) | SM(R92C_TXAGC_MCS13, power[25]) | SM(R92C_TXAGC_MCS14, power[26]) | SM(R92C_TXAGC_MCS15, power[27])); } static void urtwn_get_txpower(struct urtwn_softc *sc, int chain, struct ieee80211_channel *c, struct ieee80211_channel *extc, uint16_t power[URTWN_RIDX_COUNT]) { struct ieee80211com *ic = &sc->sc_ic; struct r92c_rom *rom = &sc->rom.r92c_rom; uint16_t cckpow, ofdmpow, htpow, diff, max; const struct urtwn_txpwr *base; int ridx, chan, group; /* Determine channel group. */ chan = ieee80211_chan2ieee(ic, c); /* XXX center freq! */ if (chan <= 3) group = 0; else if (chan <= 9) group = 1; else group = 2; /* Get original Tx power based on board type and RF chain. */ if (!(sc->chip & URTWN_CHIP_92C)) { if (sc->board_type == R92C_BOARD_TYPE_HIGHPA) base = &rtl8188ru_txagc[chain]; else base = &rtl8192cu_txagc[chain]; } else base = &rtl8192cu_txagc[chain]; memset(power, 0, URTWN_RIDX_COUNT * sizeof(power[0])); if (sc->regulatory == 0) { for (ridx = URTWN_RIDX_CCK1; ridx <= URTWN_RIDX_CCK11; ridx++) power[ridx] = base->pwr[0][ridx]; } for (ridx = URTWN_RIDX_OFDM6; ridx < URTWN_RIDX_COUNT; ridx++) { if (sc->regulatory == 3) { power[ridx] = base->pwr[0][ridx]; /* Apply vendor limits. */ if (extc != NULL) max = rom->ht40_max_pwr[group]; else max = rom->ht20_max_pwr[group]; max = (max >> (chain * 4)) & 0xf; if (power[ridx] > max) power[ridx] = max; } else if (sc->regulatory == 1) { if (extc == NULL) power[ridx] = base->pwr[group][ridx]; } else if (sc->regulatory != 2) power[ridx] = base->pwr[0][ridx]; } /* Compute per-CCK rate Tx power. */ cckpow = rom->cck_tx_pwr[chain][group]; for (ridx = URTWN_RIDX_CCK1; ridx <= URTWN_RIDX_CCK11; ridx++) { power[ridx] += cckpow; if (power[ridx] > R92C_MAX_TX_PWR) power[ridx] = R92C_MAX_TX_PWR; } htpow = rom->ht40_1s_tx_pwr[chain][group]; if (sc->ntxchains > 1) { /* Apply reduction for 2 spatial streams. */ diff = rom->ht40_2s_tx_pwr_diff[group]; diff = (diff >> (chain * 4)) & 0xf; htpow = (htpow > diff) ? htpow - diff : 0; } /* Compute per-OFDM rate Tx power. */ diff = rom->ofdm_tx_pwr_diff[group]; diff = (diff >> (chain * 4)) & 0xf; ofdmpow = htpow + diff; /* HT->OFDM correction. */ for (ridx = URTWN_RIDX_OFDM6; ridx <= URTWN_RIDX_OFDM54; ridx++) { power[ridx] += ofdmpow; if (power[ridx] > R92C_MAX_TX_PWR) power[ridx] = R92C_MAX_TX_PWR; } /* Compute per-MCS Tx power. */ if (extc == NULL) { diff = rom->ht20_tx_pwr_diff[group]; diff = (diff >> (chain * 4)) & 0xf; htpow += diff; /* HT40->HT20 correction. */ } for (ridx = 12; ridx <= 27; ridx++) { power[ridx] += htpow; if (power[ridx] > R92C_MAX_TX_PWR) power[ridx] = R92C_MAX_TX_PWR; } #ifdef USB_DEBUG if (sc->sc_debug & URTWN_DEBUG_TXPWR) { /* Dump per-rate Tx power values. */ printf("Tx power for chain %d:\n", chain); for (ridx = URTWN_RIDX_CCK1; ridx < URTWN_RIDX_COUNT; ridx++) printf("Rate %d = %u\n", ridx, power[ridx]); } #endif } static void urtwn_r88e_get_txpower(struct urtwn_softc *sc, int chain, struct ieee80211_channel *c, struct ieee80211_channel *extc, uint16_t power[URTWN_RIDX_COUNT]) { struct ieee80211com *ic = &sc->sc_ic; struct r88e_rom *rom = &sc->rom.r88e_rom; uint16_t cckpow, ofdmpow, bw20pow, htpow; const struct urtwn_r88e_txpwr *base; int ridx, chan, group; /* Determine channel group. */ chan = ieee80211_chan2ieee(ic, c); /* XXX center freq! */ if (chan <= 2) group = 0; else if (chan <= 5) group = 1; else if (chan <= 8) group = 2; else if (chan <= 11) group = 3; else if (chan <= 13) group = 4; else group = 5; /* Get original Tx power based on board type and RF chain. */ base = &rtl8188eu_txagc[chain]; memset(power, 0, URTWN_RIDX_COUNT * sizeof(power[0])); if (sc->regulatory == 0) { for (ridx = URTWN_RIDX_CCK1; ridx <= URTWN_RIDX_CCK11; ridx++) power[ridx] = base->pwr[0][ridx]; } for (ridx = URTWN_RIDX_OFDM6; ridx < URTWN_RIDX_COUNT; ridx++) { if (sc->regulatory == 3) power[ridx] = base->pwr[0][ridx]; else if (sc->regulatory == 1) { if (extc == NULL) power[ridx] = base->pwr[group][ridx]; } else if (sc->regulatory != 2) power[ridx] = base->pwr[0][ridx]; } /* Compute per-CCK rate Tx power. */ cckpow = rom->cck_tx_pwr[group]; for (ridx = URTWN_RIDX_CCK1; ridx <= URTWN_RIDX_CCK11; ridx++) { power[ridx] += cckpow; if (power[ridx] > R92C_MAX_TX_PWR) power[ridx] = R92C_MAX_TX_PWR; } htpow = rom->ht40_tx_pwr[group]; /* Compute per-OFDM rate Tx power. */ ofdmpow = htpow + sc->ofdm_tx_pwr_diff; for (ridx = URTWN_RIDX_OFDM6; ridx <= URTWN_RIDX_OFDM54; ridx++) { power[ridx] += ofdmpow; if (power[ridx] > R92C_MAX_TX_PWR) power[ridx] = R92C_MAX_TX_PWR; } bw20pow = htpow + sc->bw20_tx_pwr_diff; for (ridx = 12; ridx <= 27; ridx++) { power[ridx] += bw20pow; if (power[ridx] > R92C_MAX_TX_PWR) power[ridx] = R92C_MAX_TX_PWR; } } static void urtwn_set_txpower(struct urtwn_softc *sc, struct ieee80211_channel *c, struct ieee80211_channel *extc) { uint16_t power[URTWN_RIDX_COUNT]; int i; for (i = 0; i < sc->ntxchains; i++) { /* Compute per-rate Tx power values. */ if (sc->chip & URTWN_CHIP_88E) urtwn_r88e_get_txpower(sc, i, c, extc, power); else urtwn_get_txpower(sc, i, c, extc, power); /* Write per-rate Tx power values to hardware. */ urtwn_write_txpower(sc, i, power); } } static void urtwn_set_rx_bssid_all(struct urtwn_softc *sc, int enable) { uint32_t reg; reg = urtwn_read_4(sc, R92C_RCR); if (enable) reg &= ~R92C_RCR_CBSSID_BCN; else reg |= R92C_RCR_CBSSID_BCN; urtwn_write_4(sc, R92C_RCR, reg); } static void urtwn_set_gain(struct urtwn_softc *sc, uint8_t gain) { uint32_t reg; reg = urtwn_bb_read(sc, R92C_OFDM0_AGCCORE1(0)); reg = RW(reg, R92C_OFDM0_AGCCORE1_GAIN, gain); urtwn_bb_write(sc, R92C_OFDM0_AGCCORE1(0), reg); if (!(sc->chip & URTWN_CHIP_88E)) { reg = urtwn_bb_read(sc, R92C_OFDM0_AGCCORE1(1)); reg = RW(reg, R92C_OFDM0_AGCCORE1_GAIN, gain); urtwn_bb_write(sc, R92C_OFDM0_AGCCORE1(1), reg); } } static void urtwn_scan_start(struct ieee80211com *ic) { struct urtwn_softc *sc = ic->ic_softc; URTWN_LOCK(sc); /* Receive beacons / probe responses from any BSSID. */ if (ic->ic_opmode != IEEE80211_M_IBSS) urtwn_set_rx_bssid_all(sc, 1); /* Set gain for scanning. */ urtwn_set_gain(sc, 0x20); URTWN_UNLOCK(sc); } static void urtwn_scan_end(struct ieee80211com *ic) { struct urtwn_softc *sc = ic->ic_softc; URTWN_LOCK(sc); /* Restore limitations. */ if (ic->ic_promisc == 0 && ic->ic_opmode != IEEE80211_M_IBSS) urtwn_set_rx_bssid_all(sc, 0); /* Set gain under link. */ urtwn_set_gain(sc, 0x32); URTWN_UNLOCK(sc); } static void urtwn_set_channel(struct ieee80211com *ic) { struct urtwn_softc *sc = ic->ic_softc; struct ieee80211_channel *c = ic->ic_curchan; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); URTWN_LOCK(sc); if (vap->iv_state == IEEE80211_S_SCAN) { /* Make link LED blink during scan. */ urtwn_set_led(sc, URTWN_LED_LINK, !sc->ledlink); } urtwn_set_chan(sc, c, NULL); sc->sc_rxtap.wr_chan_freq = htole16(c->ic_freq); sc->sc_rxtap.wr_chan_flags = htole16(c->ic_flags); sc->sc_txtap.wt_chan_freq = htole16(c->ic_freq); sc->sc_txtap.wt_chan_flags = htole16(c->ic_flags); URTWN_UNLOCK(sc); } static int urtwn_wme_update(struct ieee80211com *ic) { const struct wmeParams *wmep = ic->ic_wme.wme_chanParams.cap_wmeParams; struct urtwn_softc *sc = ic->ic_softc; uint8_t aifs, acm, slottime; int ac; acm = 0; slottime = IEEE80211_GET_SLOTTIME(ic); URTWN_LOCK(sc); for (ac = WME_AC_BE; ac < WME_NUM_AC; ac++) { /* AIFS[AC] = AIFSN[AC] * aSlotTime + aSIFSTime. */ aifs = wmep[ac].wmep_aifsn * slottime + IEEE80211_DUR_SIFS; urtwn_write_4(sc, wme2queue[ac].reg, SM(R92C_EDCA_PARAM_TXOP, wmep[ac].wmep_txopLimit) | SM(R92C_EDCA_PARAM_ECWMIN, wmep[ac].wmep_logcwmin) | SM(R92C_EDCA_PARAM_ECWMAX, wmep[ac].wmep_logcwmax) | SM(R92C_EDCA_PARAM_AIFS, aifs)); if (ac != WME_AC_BE) acm |= wmep[ac].wmep_acm << ac; } if (acm != 0) acm |= R92C_ACMHWCTRL_EN; urtwn_write_1(sc, R92C_ACMHWCTRL, (urtwn_read_1(sc, R92C_ACMHWCTRL) & ~R92C_ACMHWCTRL_ACM_MASK) | acm); URTWN_UNLOCK(sc); return 0; } static void urtwn_update_slot(struct ieee80211com *ic) { urtwn_cmd_sleepable(ic->ic_softc, NULL, 0, urtwn_update_slot_cb); } static void urtwn_update_slot_cb(struct urtwn_softc *sc, union sec_param *data) { struct ieee80211com *ic = &sc->sc_ic; uint8_t slottime; slottime = IEEE80211_GET_SLOTTIME(ic); URTWN_DPRINTF(sc, URTWN_DEBUG_ANY, "%s: setting slot time to %uus\n", __func__, slottime); urtwn_write_1(sc, R92C_SLOT, slottime); urtwn_update_aifs(sc, slottime); } static void urtwn_update_aifs(struct urtwn_softc *sc, uint8_t slottime) { const struct wmeParams *wmep = sc->sc_ic.ic_wme.wme_chanParams.cap_wmeParams; uint8_t aifs, ac; for (ac = WME_AC_BE; ac < WME_NUM_AC; ac++) { /* AIFS[AC] = AIFSN[AC] * aSlotTime + aSIFSTime. */ aifs = wmep[ac].wmep_aifsn * slottime + IEEE80211_DUR_SIFS; urtwn_write_1(sc, wme2queue[ac].reg, aifs); } } static void urtwn_set_promisc(struct urtwn_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint32_t rcr, mask1, mask2; URTWN_ASSERT_LOCKED(sc); if (vap->iv_opmode == IEEE80211_M_MONITOR) return; mask1 = R92C_RCR_ACF | R92C_RCR_ADF | R92C_RCR_AMF | R92C_RCR_AAP; mask2 = R92C_RCR_APM; if (vap->iv_state == IEEE80211_S_RUN) { switch (vap->iv_opmode) { case IEEE80211_M_STA: mask2 |= R92C_RCR_CBSSID_DATA; /* FALLTHROUGH */ case IEEE80211_M_HOSTAP: mask2 |= R92C_RCR_CBSSID_BCN; break; case IEEE80211_M_IBSS: mask2 |= R92C_RCR_CBSSID_DATA; break; default: device_printf(sc->sc_dev, "%s: undefined opmode %d\n", __func__, vap->iv_opmode); return; } } rcr = urtwn_read_4(sc, R92C_RCR); if (ic->ic_promisc == 0) rcr = (rcr & ~mask1) | mask2; else rcr = (rcr & ~mask2) | mask1; urtwn_write_4(sc, R92C_RCR, rcr); } static void urtwn_update_promisc(struct ieee80211com *ic) { struct urtwn_softc *sc = ic->ic_softc; URTWN_LOCK(sc); if (sc->sc_flags & URTWN_RUNNING) urtwn_set_promisc(sc); URTWN_UNLOCK(sc); } static void urtwn_update_mcast(struct ieee80211com *ic) { /* XXX do nothing? */ } static struct ieee80211_node * urtwn_r88e_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) { struct urtwn_node *un; un = malloc(sizeof (struct urtwn_node), M_80211_NODE, M_NOWAIT | M_ZERO); if (un == NULL) return NULL; un->id = URTWN_MACID_UNDEFINED; return &un->ni; } static void urtwn_r88e_newassoc(struct ieee80211_node *ni, int isnew) { struct urtwn_softc *sc = ni->ni_ic->ic_softc; struct urtwn_node *un = URTWN_NODE(ni); uint8_t id; if (!isnew) return; URTWN_NT_LOCK(sc); for (id = 0; id <= URTWN_MACID_MAX(sc); id++) { if (id != URTWN_MACID_BC && sc->node_list[id] == NULL) { un->id = id; sc->node_list[id] = ni; break; } } URTWN_NT_UNLOCK(sc); if (id > URTWN_MACID_MAX(sc)) { device_printf(sc->sc_dev, "%s: node table is full\n", __func__); } } static void urtwn_r88e_node_free(struct ieee80211_node *ni) { struct urtwn_softc *sc = ni->ni_ic->ic_softc; struct urtwn_node *un = URTWN_NODE(ni); URTWN_NT_LOCK(sc); if (un->id != URTWN_MACID_UNDEFINED) sc->node_list[un->id] = NULL; URTWN_NT_UNLOCK(sc); sc->sc_node_free(ni); } static void urtwn_set_chan(struct urtwn_softc *sc, struct ieee80211_channel *c, struct ieee80211_channel *extc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t reg; u_int chan; int i; chan = ieee80211_chan2ieee(ic, c); /* XXX center freq! */ if (chan == 0 || chan == IEEE80211_CHAN_ANY) { device_printf(sc->sc_dev, "%s: invalid channel %x\n", __func__, chan); return; } /* Set Tx power for this new channel. */ urtwn_set_txpower(sc, c, extc); for (i = 0; i < sc->nrxchains; i++) { urtwn_rf_write(sc, i, R92C_RF_CHNLBW, RW(sc->rf_chnlbw[i], R92C_RF_CHNLBW_CHNL, chan)); } #ifndef IEEE80211_NO_HT if (extc != NULL) { /* Is secondary channel below or above primary? */ int prichlo = c->ic_freq < extc->ic_freq; urtwn_write_1(sc, R92C_BWOPMODE, urtwn_read_1(sc, R92C_BWOPMODE) & ~R92C_BWOPMODE_20MHZ); reg = urtwn_read_1(sc, R92C_RRSR + 2); reg = (reg & ~0x6f) | (prichlo ? 1 : 2) << 5; urtwn_write_1(sc, R92C_RRSR + 2, reg); urtwn_bb_write(sc, R92C_FPGA0_RFMOD, urtwn_bb_read(sc, R92C_FPGA0_RFMOD) | R92C_RFMOD_40MHZ); urtwn_bb_write(sc, R92C_FPGA1_RFMOD, urtwn_bb_read(sc, R92C_FPGA1_RFMOD) | R92C_RFMOD_40MHZ); /* Set CCK side band. */ reg = urtwn_bb_read(sc, R92C_CCK0_SYSTEM); reg = (reg & ~0x00000010) | (prichlo ? 0 : 1) << 4; urtwn_bb_write(sc, R92C_CCK0_SYSTEM, reg); reg = urtwn_bb_read(sc, R92C_OFDM1_LSTF); reg = (reg & ~0x00000c00) | (prichlo ? 1 : 2) << 10; urtwn_bb_write(sc, R92C_OFDM1_LSTF, reg); urtwn_bb_write(sc, R92C_FPGA0_ANAPARAM2, urtwn_bb_read(sc, R92C_FPGA0_ANAPARAM2) & ~R92C_FPGA0_ANAPARAM2_CBW20); reg = urtwn_bb_read(sc, 0x818); reg = (reg & ~0x0c000000) | (prichlo ? 2 : 1) << 26; urtwn_bb_write(sc, 0x818, reg); /* Select 40MHz bandwidth. */ urtwn_rf_write(sc, 0, R92C_RF_CHNLBW, (sc->rf_chnlbw[0] & ~0xfff) | chan); } else #endif { urtwn_write_1(sc, R92C_BWOPMODE, urtwn_read_1(sc, R92C_BWOPMODE) | R92C_BWOPMODE_20MHZ); urtwn_bb_write(sc, R92C_FPGA0_RFMOD, urtwn_bb_read(sc, R92C_FPGA0_RFMOD) & ~R92C_RFMOD_40MHZ); urtwn_bb_write(sc, R92C_FPGA1_RFMOD, urtwn_bb_read(sc, R92C_FPGA1_RFMOD) & ~R92C_RFMOD_40MHZ); if (!(sc->chip & URTWN_CHIP_88E)) { urtwn_bb_write(sc, R92C_FPGA0_ANAPARAM2, urtwn_bb_read(sc, R92C_FPGA0_ANAPARAM2) | R92C_FPGA0_ANAPARAM2_CBW20); } /* Select 20MHz bandwidth. */ urtwn_rf_write(sc, 0, R92C_RF_CHNLBW, (sc->rf_chnlbw[0] & ~0xfff) | chan | ((sc->chip & URTWN_CHIP_88E) ? R88E_RF_CHNLBW_BW20 : R92C_RF_CHNLBW_BW20)); } } static void urtwn_iq_calib(struct urtwn_softc *sc) { /* TODO */ } static void urtwn_lc_calib(struct urtwn_softc *sc) { uint32_t rf_ac[2]; uint8_t txmode; int i; txmode = urtwn_read_1(sc, R92C_OFDM1_LSTF + 3); if ((txmode & 0x70) != 0) { /* Disable all continuous Tx. */ urtwn_write_1(sc, R92C_OFDM1_LSTF + 3, txmode & ~0x70); /* Set RF mode to standby mode. */ for (i = 0; i < sc->nrxchains; i++) { rf_ac[i] = urtwn_rf_read(sc, i, R92C_RF_AC); urtwn_rf_write(sc, i, R92C_RF_AC, RW(rf_ac[i], R92C_RF_AC_MODE, R92C_RF_AC_MODE_STANDBY)); } } else { /* Block all Tx queues. */ urtwn_write_1(sc, R92C_TXPAUSE, R92C_TX_QUEUE_ALL); } /* Start calibration. */ urtwn_rf_write(sc, 0, R92C_RF_CHNLBW, urtwn_rf_read(sc, 0, R92C_RF_CHNLBW) | R92C_RF_CHNLBW_LCSTART); /* Give calibration the time to complete. */ usb_pause_mtx(&sc->sc_mtx, hz / 10); /* 100ms */ /* Restore configuration. */ if ((txmode & 0x70) != 0) { /* Restore Tx mode. */ urtwn_write_1(sc, R92C_OFDM1_LSTF + 3, txmode); /* Restore RF mode. */ for (i = 0; i < sc->nrxchains; i++) urtwn_rf_write(sc, i, R92C_RF_AC, rf_ac[i]); } else { /* Unblock all Tx queues. */ urtwn_write_1(sc, R92C_TXPAUSE, 0x00); } } static void urtwn_temp_calib(struct urtwn_softc *sc) { uint8_t temp; URTWN_ASSERT_LOCKED(sc); if (!(sc->sc_flags & URTWN_TEMP_MEASURED)) { /* Start measuring temperature. */ URTWN_DPRINTF(sc, URTWN_DEBUG_TEMP, "%s: start measuring temperature\n", __func__); if (sc->chip & URTWN_CHIP_88E) { urtwn_rf_write(sc, 0, R88E_RF_T_METER, R88E_RF_T_METER_START); } else { urtwn_rf_write(sc, 0, R92C_RF_T_METER, R92C_RF_T_METER_START); } sc->sc_flags |= URTWN_TEMP_MEASURED; return; } sc->sc_flags &= ~URTWN_TEMP_MEASURED; /* Read measured temperature. */ if (sc->chip & URTWN_CHIP_88E) { temp = MS(urtwn_rf_read(sc, 0, R88E_RF_T_METER), R88E_RF_T_METER_VAL); } else { temp = MS(urtwn_rf_read(sc, 0, R92C_RF_T_METER), R92C_RF_T_METER_VAL); } if (temp == 0) { /* Read failed, skip. */ URTWN_DPRINTF(sc, URTWN_DEBUG_TEMP, "%s: temperature read failed, skipping\n", __func__); return; } URTWN_DPRINTF(sc, URTWN_DEBUG_TEMP, "%s: temperature: previous %u, current %u\n", __func__, sc->thcal_lctemp, temp); /* * Redo LC calibration if temperature changed significantly since * last calibration. */ if (sc->thcal_lctemp == 0) { /* First LC calibration is performed in urtwn_init(). */ sc->thcal_lctemp = temp; } else if (abs(temp - sc->thcal_lctemp) > 1) { URTWN_DPRINTF(sc, URTWN_DEBUG_TEMP, "%s: LC calib triggered by temp: %u -> %u\n", __func__, sc->thcal_lctemp, temp); urtwn_lc_calib(sc); /* Record temperature of last LC calibration. */ sc->thcal_lctemp = temp; } } static int urtwn_init(struct urtwn_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint8_t macaddr[IEEE80211_ADDR_LEN]; uint32_t reg; usb_error_t usb_err = USB_ERR_NORMAL_COMPLETION; int error; URTWN_LOCK(sc); if (sc->sc_flags & URTWN_RUNNING) { URTWN_UNLOCK(sc); return (0); } /* Init firmware commands ring. */ sc->fwcur = 0; /* Allocate Tx/Rx buffers. */ error = urtwn_alloc_rx_list(sc); if (error != 0) goto fail; error = urtwn_alloc_tx_list(sc); if (error != 0) goto fail; /* Power on adapter. */ error = urtwn_power_on(sc); if (error != 0) goto fail; /* Initialize DMA. */ error = urtwn_dma_init(sc); if (error != 0) goto fail; /* Set info size in Rx descriptors (in 64-bit words). */ urtwn_write_1(sc, R92C_RX_DRVINFO_SZ, 4); /* Init interrupts. */ if (sc->chip & URTWN_CHIP_88E) { usb_err = urtwn_write_4(sc, R88E_HISR, 0xffffffff); if (usb_err != USB_ERR_NORMAL_COMPLETION) goto fail; usb_err = urtwn_write_4(sc, R88E_HIMR, R88E_HIMR_CPWM | R88E_HIMR_CPWM2 | R88E_HIMR_TBDER | R88E_HIMR_PSTIMEOUT); if (usb_err != USB_ERR_NORMAL_COMPLETION) goto fail; usb_err = urtwn_write_4(sc, R88E_HIMRE, R88E_HIMRE_RXFOVW | R88E_HIMRE_TXFOVW | R88E_HIMRE_RXERR | R88E_HIMRE_TXERR); if (usb_err != USB_ERR_NORMAL_COMPLETION) goto fail; usb_err = urtwn_write_1(sc, R92C_USB_SPECIAL_OPTION, urtwn_read_1(sc, R92C_USB_SPECIAL_OPTION) | R92C_USB_SPECIAL_OPTION_INT_BULK_SEL); if (usb_err != USB_ERR_NORMAL_COMPLETION) goto fail; } else { usb_err = urtwn_write_4(sc, R92C_HISR, 0xffffffff); if (usb_err != USB_ERR_NORMAL_COMPLETION) goto fail; usb_err = urtwn_write_4(sc, R92C_HIMR, 0xffffffff); if (usb_err != USB_ERR_NORMAL_COMPLETION) goto fail; } /* Set MAC address. */ IEEE80211_ADDR_COPY(macaddr, vap ? vap->iv_myaddr : ic->ic_macaddr); usb_err = urtwn_write_region_1(sc, R92C_MACID, macaddr, IEEE80211_ADDR_LEN); if (usb_err != USB_ERR_NORMAL_COMPLETION) goto fail; /* Set initial network type. */ urtwn_set_mode(sc, R92C_MSR_INFRA); /* Initialize Rx filter. */ urtwn_rxfilter_init(sc); /* Set response rate. */ reg = urtwn_read_4(sc, R92C_RRSR); reg = RW(reg, R92C_RRSR_RATE_BITMAP, R92C_RRSR_RATE_CCK_ONLY_1M); urtwn_write_4(sc, R92C_RRSR, reg); /* Set short/long retry limits. */ urtwn_write_2(sc, R92C_RL, SM(R92C_RL_SRL, 0x30) | SM(R92C_RL_LRL, 0x30)); /* Initialize EDCA parameters. */ urtwn_edca_init(sc); /* Setup rate fallback. */ if (!(sc->chip & URTWN_CHIP_88E)) { urtwn_write_4(sc, R92C_DARFRC + 0, 0x00000000); urtwn_write_4(sc, R92C_DARFRC + 4, 0x10080404); urtwn_write_4(sc, R92C_RARFRC + 0, 0x04030201); urtwn_write_4(sc, R92C_RARFRC + 4, 0x08070605); } urtwn_write_1(sc, R92C_FWHW_TXQ_CTRL, urtwn_read_1(sc, R92C_FWHW_TXQ_CTRL) | R92C_FWHW_TXQ_CTRL_AMPDU_RTY_NEW); /* Set ACK timeout. */ urtwn_write_1(sc, R92C_ACKTO, 0x40); /* Setup USB aggregation. */ reg = urtwn_read_4(sc, R92C_TDECTRL); reg = RW(reg, R92C_TDECTRL_BLK_DESC_NUM, 6); urtwn_write_4(sc, R92C_TDECTRL, reg); urtwn_write_1(sc, R92C_TRXDMA_CTRL, urtwn_read_1(sc, R92C_TRXDMA_CTRL) | R92C_TRXDMA_CTRL_RXDMA_AGG_EN); urtwn_write_1(sc, R92C_RXDMA_AGG_PG_TH, 48); if (sc->chip & URTWN_CHIP_88E) urtwn_write_1(sc, R92C_RXDMA_AGG_PG_TH + 1, 4); else { urtwn_write_1(sc, R92C_USB_DMA_AGG_TO, 4); urtwn_write_1(sc, R92C_USB_SPECIAL_OPTION, urtwn_read_1(sc, R92C_USB_SPECIAL_OPTION) | R92C_USB_SPECIAL_OPTION_AGG_EN); urtwn_write_1(sc, R92C_USB_AGG_TH, 8); urtwn_write_1(sc, R92C_USB_AGG_TO, 6); } /* Initialize beacon parameters. */ urtwn_write_2(sc, R92C_BCN_CTRL, 0x1010); urtwn_write_2(sc, R92C_TBTT_PROHIBIT, 0x6404); urtwn_write_1(sc, R92C_DRVERLYINT, 0x05); urtwn_write_1(sc, R92C_BCNDMATIM, 0x02); urtwn_write_2(sc, R92C_BCNTCFG, 0x660f); if (!(sc->chip & URTWN_CHIP_88E)) { /* Setup AMPDU aggregation. */ urtwn_write_4(sc, R92C_AGGLEN_LMT, 0x99997631); /* MCS7~0 */ urtwn_write_1(sc, R92C_AGGR_BREAK_TIME, 0x16); urtwn_write_2(sc, R92C_MAX_AGGR_NUM, 0x0708); urtwn_write_1(sc, R92C_BCN_MAX_ERR, 0xff); } #ifndef URTWN_WITHOUT_UCODE /* Load 8051 microcode. */ error = urtwn_load_firmware(sc); if (error == 0) sc->sc_flags |= URTWN_FW_LOADED; #endif /* Initialize MAC/BB/RF blocks. */ error = urtwn_mac_init(sc); if (error != 0) { device_printf(sc->sc_dev, "%s: error while initializing MAC block\n", __func__); goto fail; } urtwn_bb_init(sc); urtwn_rf_init(sc); /* Reinitialize Rx filter (D3845 is not committed yet). */ urtwn_rxfilter_init(sc); if (sc->chip & URTWN_CHIP_88E) { urtwn_write_2(sc, R92C_CR, urtwn_read_2(sc, R92C_CR) | R92C_CR_MACTXEN | R92C_CR_MACRXEN); } /* Turn CCK and OFDM blocks on. */ reg = urtwn_bb_read(sc, R92C_FPGA0_RFMOD); reg |= R92C_RFMOD_CCK_EN; usb_err = urtwn_bb_write(sc, R92C_FPGA0_RFMOD, reg); if (usb_err != USB_ERR_NORMAL_COMPLETION) goto fail; reg = urtwn_bb_read(sc, R92C_FPGA0_RFMOD); reg |= R92C_RFMOD_OFDM_EN; usb_err = urtwn_bb_write(sc, R92C_FPGA0_RFMOD, reg); if (usb_err != USB_ERR_NORMAL_COMPLETION) goto fail; /* Clear per-station keys table. */ urtwn_cam_init(sc); /* Enable decryption / encryption. */ urtwn_write_2(sc, R92C_SECCFG, R92C_SECCFG_TXUCKEY_DEF | R92C_SECCFG_RXUCKEY_DEF | R92C_SECCFG_TXENC_ENA | R92C_SECCFG_RXDEC_ENA | R92C_SECCFG_TXBCKEY_DEF | R92C_SECCFG_RXBCKEY_DEF); /* * Install static keys (if any). * Must be called after urtwn_cam_init(). */ ieee80211_runtask(ic, &sc->cmdq_task); /* Enable hardware sequence numbering. */ urtwn_write_1(sc, R92C_HWSEQ_CTRL, R92C_TX_QUEUE_ALL); /* Enable per-packet TX report. */ if (sc->chip & URTWN_CHIP_88E) { urtwn_write_1(sc, R88E_TX_RPT_CTRL, urtwn_read_1(sc, R88E_TX_RPT_CTRL) | R88E_TX_RPT1_ENA); } /* Perform LO and IQ calibrations. */ urtwn_iq_calib(sc); /* Perform LC calibration. */ urtwn_lc_calib(sc); /* Fix USB interference issue. */ if (!(sc->chip & URTWN_CHIP_88E)) { urtwn_write_1(sc, 0xfe40, 0xe0); urtwn_write_1(sc, 0xfe41, 0x8d); urtwn_write_1(sc, 0xfe42, 0x80); urtwn_pa_bias_init(sc); } /* Initialize GPIO setting. */ urtwn_write_1(sc, R92C_GPIO_MUXCFG, urtwn_read_1(sc, R92C_GPIO_MUXCFG) & ~R92C_GPIO_MUXCFG_ENBT); /* Fix for lower temperature. */ if (!(sc->chip & URTWN_CHIP_88E)) urtwn_write_1(sc, 0x15, 0xe9); usbd_transfer_start(sc->sc_xfer[URTWN_BULK_RX]); sc->sc_flags |= URTWN_RUNNING; callout_reset(&sc->sc_watchdog_ch, hz, urtwn_watchdog, sc); fail: if (usb_err != USB_ERR_NORMAL_COMPLETION) error = EIO; URTWN_UNLOCK(sc); return (error); } static void urtwn_stop(struct urtwn_softc *sc) { URTWN_LOCK(sc); if (!(sc->sc_flags & URTWN_RUNNING)) { URTWN_UNLOCK(sc); return; } sc->sc_flags &= ~(URTWN_RUNNING | URTWN_FW_LOADED | URTWN_TEMP_MEASURED); sc->thcal_lctemp = 0; callout_stop(&sc->sc_watchdog_ch); urtwn_abort_xfers(sc); urtwn_drain_mbufq(sc); urtwn_power_off(sc); URTWN_UNLOCK(sc); } static void urtwn_abort_xfers(struct urtwn_softc *sc) { int i; URTWN_ASSERT_LOCKED(sc); /* abort any pending transfers */ for (i = 0; i < URTWN_N_TRANSFER; i++) usbd_transfer_stop(sc->sc_xfer[i]); } static int urtwn_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct urtwn_softc *sc = ic->ic_softc; struct urtwn_data *bf; int error; + + URTWN_DPRINTF(sc, URTWN_DEBUG_XMIT, "%s: called; m=%p\n", + __func__, + m); /* prevent management frames from being sent if we're not ready */ URTWN_LOCK(sc); if (!(sc->sc_flags & URTWN_RUNNING)) { error = ENETDOWN; goto end; } bf = urtwn_getbuf(sc); if (bf == NULL) { error = ENOBUFS; goto end; } if (params == NULL) { /* * Legacy path; interpret frame contents to decide * precisely how to send the frame. */ error = urtwn_tx_data(sc, ni, m, bf); } else { /* * Caller supplied explicit parameters to use in * sending the frame. */ error = urtwn_tx_raw(sc, ni, m, bf, params); } if (error != 0) { STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); goto end; } sc->sc_txtimer = 5; callout_reset(&sc->sc_watchdog_ch, hz, urtwn_watchdog, sc); end: if (error != 0) m_freem(m); URTWN_UNLOCK(sc); return (error); } static void urtwn_ms_delay(struct urtwn_softc *sc) { usb_pause_mtx(&sc->sc_mtx, hz / 1000); } static device_method_t urtwn_methods[] = { /* Device interface */ DEVMETHOD(device_probe, urtwn_match), DEVMETHOD(device_attach, urtwn_attach), DEVMETHOD(device_detach, urtwn_detach), DEVMETHOD_END }; static driver_t urtwn_driver = { "urtwn", urtwn_methods, sizeof(struct urtwn_softc) }; static devclass_t urtwn_devclass; DRIVER_MODULE(urtwn, uhub, urtwn_driver, urtwn_devclass, NULL, NULL); MODULE_DEPEND(urtwn, usb, 1, 1, 1); MODULE_DEPEND(urtwn, wlan, 1, 1, 1); #ifndef URTWN_WITHOUT_UCODE MODULE_DEPEND(urtwn, firmware, 1, 1, 1); #endif MODULE_VERSION(urtwn, 1); USB_PNP_HOST_INFO(urtwn_devs); Index: projects/release-pkg/sys/dev/urtwn/if_urtwnvar.h =================================================================== --- projects/release-pkg/sys/dev/urtwn/if_urtwnvar.h (revision 297604) +++ projects/release-pkg/sys/dev/urtwn/if_urtwnvar.h (revision 297605) @@ -1,236 +1,239 @@ /*- * Copyright (c) 2010 Damien Bergamini * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $OpenBSD: if_urtwnreg.h,v 1.3 2010/11/16 18:02:59 damien Exp $ * $FreeBSD$ */ -#define URTWN_RX_LIST_COUNT 1 +#define URTWN_RX_LIST_COUNT 64 #define URTWN_TX_LIST_COUNT 8 #define URTWN_HOST_CMD_RING_COUNT 32 -#define URTWN_RXBUFSZ (16 * 1024) -#define URTWN_TXBUFSZ (sizeof(struct r92c_tx_desc) + IEEE80211_MAX_LEN) +#define URTWN_RXBUFSZ (8 * 1024) +//#define URTWN_TXBUFSZ (sizeof(struct r92c_tx_desc) + IEEE80211_MAX_LEN) +/* Leave enough space for an A-MSDU frame */ +#define URTWN_TXBUFSZ (16 * 1024) #define URTWN_RX_DESC_SIZE (sizeof(struct r92c_rx_stat)) #define URTWN_TX_DESC_SIZE (sizeof(struct r92c_tx_desc)) #define URTWN_TX_TIMEOUT 5000 /* ms */ #define URTWN_LED_LINK 0 #define URTWN_LED_DATA 1 struct urtwn_rx_radiotap_header { struct ieee80211_radiotap_header wr_ihdr; uint64_t wr_tsft; uint8_t wr_flags; uint8_t wr_rate; uint16_t wr_chan_freq; uint16_t wr_chan_flags; int8_t wr_dbm_antsignal; int8_t wr_dbm_antnoise; } __packed __aligned(8); #define URTWN_RX_RADIOTAP_PRESENT \ (1 << IEEE80211_RADIOTAP_TSFT | \ 1 << IEEE80211_RADIOTAP_FLAGS | \ 1 << IEEE80211_RADIOTAP_RATE | \ 1 << IEEE80211_RADIOTAP_CHANNEL | \ 1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL | \ 1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) struct urtwn_tx_radiotap_header { struct ieee80211_radiotap_header wt_ihdr; uint8_t wt_flags; uint16_t wt_chan_freq; uint16_t wt_chan_flags; } __packed __aligned(8); #define URTWN_TX_RADIOTAP_PRESENT \ (1 << IEEE80211_RADIOTAP_FLAGS | \ 1 << IEEE80211_RADIOTAP_CHANNEL) struct urtwn_softc; struct urtwn_data { struct urtwn_softc *sc; uint8_t *buf; uint16_t buflen; struct mbuf *m; struct ieee80211_node *ni; STAILQ_ENTRY(urtwn_data) next; }; typedef STAILQ_HEAD(, urtwn_data) urtwn_datahead; union sec_param { struct ieee80211_key key; }; #define CMD_FUNC_PROTO void (*func)(struct urtwn_softc *, \ union sec_param *) struct urtwn_cmdq { union sec_param data; CMD_FUNC_PROTO; }; #define URTWN_CMDQ_SIZE 16 struct urtwn_fw_info { const uint8_t *data; size_t size; }; struct urtwn_node { struct ieee80211_node ni; /* must be the first */ uint8_t id; }; #define URTWN_NODE(ni) ((struct urtwn_node *)(ni)) struct urtwn_vap { struct ieee80211vap vap; struct r92c_tx_desc bcn_desc; struct mbuf *bcn_mbuf; struct task tsf_task_adhoc; int (*newstate)(struct ieee80211vap *, enum ieee80211_state, int); void (*recv_mgmt)(struct ieee80211_node *, struct mbuf *, int, const struct ieee80211_rx_stats *, int, int); }; #define URTWN_VAP(vap) ((struct urtwn_vap *)(vap)) struct urtwn_host_cmd { void (*cb)(struct urtwn_softc *, void *); uint8_t data[256]; }; struct urtwn_cmd_newstate { enum ieee80211_state state; int arg; }; struct urtwn_cmd_key { struct ieee80211_key key; uint16_t associd; }; enum { URTWN_BULK_RX, URTWN_BULK_TX_BE, /* = WME_AC_BE */ URTWN_BULK_TX_BK, /* = WME_AC_BK */ URTWN_BULK_TX_VI, /* = WME_AC_VI */ URTWN_BULK_TX_VO, /* = WME_AC_VI */ URTWN_N_TRANSFER = 5, }; #define URTWN_EP_QUEUES URTWN_BULK_RX union urtwn_rom { struct r92c_rom r92c_rom; struct r88e_rom r88e_rom; }; struct urtwn_softc { struct ieee80211com sc_ic; struct mbufq sc_snd; device_t sc_dev; struct usb_device *sc_udev; uint32_t sc_debug; uint8_t sc_iface_index; uint8_t sc_flags; #define URTWN_FLAG_CCK_HIPWR 0x01 #define URTWN_DETACHED 0x02 #define URTWN_RUNNING 0x04 #define URTWN_FW_LOADED 0x08 #define URTWN_TEMP_MEASURED 0x10 u_int chip; #define URTWN_CHIP_92C 0x01 #define URTWN_CHIP_92C_1T2R 0x02 #define URTWN_CHIP_UMC 0x04 #define URTWN_CHIP_UMC_A_CUT 0x08 #define URTWN_CHIP_88E 0x10 #define URTWN_CHIP_HAS_RATECTL(_sc) (!!((_sc)->chip & URTWN_CHIP_88E)) void (*sc_node_free)(struct ieee80211_node *); void (*sc_rf_write)(struct urtwn_softc *, int, uint8_t, uint32_t); int (*sc_power_on)(struct urtwn_softc *); void (*sc_power_off)(struct urtwn_softc *); struct ieee80211_node *node_list[R88E_MACID_MAX + 1]; struct mtx nt_mtx; uint8_t board_type; uint8_t regulatory; uint8_t pa_setting; int8_t ofdm_tx_pwr_diff; int8_t bw20_tx_pwr_diff; int avg_pwdb; uint8_t thcal_lctemp; int ntxchains; int nrxchains; int ledlink; int sc_txtimer; int fwcur; struct urtwn_data sc_rx[URTWN_RX_LIST_COUNT]; urtwn_datahead sc_rx_active; urtwn_datahead sc_rx_inactive; struct urtwn_data sc_tx[URTWN_TX_LIST_COUNT]; urtwn_datahead sc_tx_active; + int sc_tx_n_active; urtwn_datahead sc_tx_inactive; urtwn_datahead sc_tx_pending; union urtwn_rom rom; uint16_t last_rom_addr; struct callout sc_calib_to; struct callout sc_watchdog_ch; struct mtx sc_mtx; uint32_t keys_bmap; struct urtwn_cmdq cmdq[URTWN_CMDQ_SIZE]; struct mtx cmdq_mtx; struct task cmdq_task; uint8_t cmdq_first; uint8_t cmdq_last; uint32_t rf_chnlbw[R92C_MAX_CHAINS]; struct usb_xfer *sc_xfer[URTWN_N_TRANSFER]; struct urtwn_rx_radiotap_header sc_rxtap; struct urtwn_tx_radiotap_header sc_txtap; }; #define URTWN_LOCK(sc) mtx_lock(&(sc)->sc_mtx) #define URTWN_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) #define URTWN_ASSERT_LOCKED(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED) #define URTWN_CMDQ_LOCK_INIT(sc) \ mtx_init(&(sc)->cmdq_mtx, "cmdq lock", NULL, MTX_DEF) #define URTWN_CMDQ_LOCK(sc) mtx_lock(&(sc)->cmdq_mtx) #define URTWN_CMDQ_UNLOCK(sc) mtx_unlock(&(sc)->cmdq_mtx) #define URTWN_CMDQ_LOCK_DESTROY(sc) mtx_destroy(&(sc)->cmdq_mtx) #define URTWN_NT_LOCK_INIT(sc) \ mtx_init(&(sc)->nt_mtx, "node table lock", NULL, MTX_DEF) #define URTWN_NT_LOCK(sc) mtx_lock(&(sc)->nt_mtx) #define URTWN_NT_UNLOCK(sc) mtx_unlock(&(sc)->nt_mtx) #define URTWN_NT_LOCK_DESTROY(sc) mtx_destroy(&(sc)->nt_mtx) Index: projects/release-pkg/sys/dev/usb/controller/ehci_fsl.c =================================================================== --- projects/release-pkg/sys/dev/usb/controller/ehci_fsl.c (revision 297604) +++ projects/release-pkg/sys/dev/usb/controller/ehci_fsl.c (revision 297605) @@ -1,427 +1,427 @@ /*- * Copyright (c) 2010-2012 Semihalf * 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_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 #include #include #include #include "opt_platform.h" /* * Register the driver */ /* Forward declarations */ static int fsl_ehci_attach(device_t self); static int fsl_ehci_detach(device_t self); static int fsl_ehci_probe(device_t self); static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, fsl_ehci_probe), DEVMETHOD(device_attach, fsl_ehci_attach), DEVMETHOD(device_detach, fsl_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), { 0, 0 } }; /* kobj_class definition */ static driver_t ehci_driver = { "ehci", ehci_methods, sizeof(struct ehci_softc) }; static devclass_t ehci_devclass; DRIVER_MODULE(ehci, simplebus, ehci_driver, ehci_devclass, 0, 0); MODULE_DEPEND(ehci, usb, 1, 1, 1); /* * Private defines */ #define FSL_EHCI_REG_OFF 0x100 #define FSL_EHCI_REG_SIZE 0x300 /* * Internal interface registers' offsets. * Offsets from 0x000 ehci dev space, big-endian access. */ enum internal_reg { SNOOP1 = 0x400, SNOOP2 = 0x404, AGE_CNT_THRESH = 0x408, SI_CTRL = 0x410, CONTROL = 0x500 }; /* CONTROL register bit flags */ enum control_flags { USB_EN = 0x00000004, UTMI_PHY_EN = 0x00000200, ULPI_INT_EN = 0x00000001 }; /* SI_CTRL register bit flags */ enum si_ctrl_flags { FETCH_32 = 1, FETCH_64 = 0 }; #define SNOOP_RANGE_2GB 0x1E /* * Operational registers' offsets. * Offsets from USBCMD register, little-endian access. */ enum special_op_reg { USBMODE = 0x0A8, PORTSC = 0x084, ULPI_VIEWPORT = 0x70 }; /* USBMODE register bit flags */ enum usbmode_flags { HOST_MODE = 0x3, DEVICE_MODE = 0x2 }; #define PORT_POWER_MASK 0x00001000 /* * Private methods */ static void set_to_host_mode(ehci_softc_t *sc) { int tmp; tmp = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl, USBMODE); bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, USBMODE, tmp | HOST_MODE); } static void enable_usb(device_t dev, bus_space_tag_t iot, bus_space_handle_t ioh) { int tmp; phandle_t node; char *phy_type; phy_type = NULL; tmp = bus_space_read_4(iot, ioh, CONTROL) | USB_EN; node = ofw_bus_get_node(dev); if ((node != 0) && (OF_getprop_alloc(node, "phy_type", 1, (void **)&phy_type) > 0)) { if (strncasecmp(phy_type, "utmi", strlen("utmi")) == 0) tmp |= UTMI_PHY_EN; free(phy_type, M_OFWPROP); } bus_space_write_4(iot, ioh, CONTROL, tmp); } static void set_32b_prefetch(bus_space_tag_t iot, bus_space_handle_t ioh) { bus_space_write_4(iot, ioh, SI_CTRL, FETCH_32); } static void set_snooping(bus_space_tag_t iot, bus_space_handle_t ioh) { bus_space_write_4(iot, ioh, SNOOP1, SNOOP_RANGE_2GB); bus_space_write_4(iot, ioh, SNOOP2, 0x80000000 | SNOOP_RANGE_2GB); } static void clear_port_power(ehci_softc_t *sc) { int tmp; tmp = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl, PORTSC); bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, PORTSC, tmp & ~PORT_POWER_MASK); } /* * Public methods */ static int fsl_ehci_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (((ofw_bus_is_compatible(dev, "fsl-usb2-dr")) == 0) && ((ofw_bus_is_compatible(dev, "fsl-usb2-mph")) == 0)) return (ENXIO); device_set_desc(dev, "Freescale integrated EHCI controller"); return (BUS_PROBE_DEFAULT); } static int fsl_ehci_attach(device_t self) { ehci_softc_t *sc; int rid; int err; bus_space_handle_t ioh; bus_space_tag_t iot; sc = device_get_softc(self); rid = 0; 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; if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) return (ENOMEM); /* Allocate io resource for EHCI */ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_io_res == NULL) { err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } iot = rman_get_bustag(sc->sc_io_res); /* * Set handle to USB related registers subregion used by generic * EHCI driver */ ioh = rman_get_bushandle(sc->sc_io_res); err = bus_space_subregion(iot, ioh, FSL_EHCI_REG_OFF, FSL_EHCI_REG_SIZE, &sc->sc_io_hdl); if (err != 0) { err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } /* Set little-endian tag for use by the generic EHCI driver */ sc->sc_io_tag = &bs_le_tag; /* Allocate irq */ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->sc_irq_res == NULL) { err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } /* Setup interrupt handler */ - err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO, + 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; err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } /* Add USB device */ sc->sc_bus.bdev = device_add_child(self, "usbus", -1); if (!sc->sc_bus.bdev) { device_printf(self, "Could not add USB device\n"); err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENOMEM); } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); sc->sc_id_vendor = 0x1234; strlcpy(sc->sc_vendor, "Freescale", sizeof(sc->sc_vendor)); /* Enable USB */ err = ehci_reset(sc); if (err) { device_printf(self, "Could not reset the controller\n"); err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } enable_usb(self, iot, ioh); set_snooping(iot, ioh); set_to_host_mode(sc); set_32b_prefetch(iot, ioh); /* * If usb subsystem is enabled in U-Boot, port power has to be turned * off to allow proper discovery of devices during boot up. */ clear_port_power(sc); /* Set flags */ sc->sc_flags |= EHCI_SCFLG_DONTRESET | EHCI_SCFLG_NORESTERM; err = ehci_init(sc); if (!err) { sc->sc_flags |= EHCI_SCFLG_DONEINIT; err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) { device_printf(self, "USB init failed err=%d\n", err); err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (EIO); } return (0); } static int fsl_ehci_detach(device_t self) { int err; ehci_softc_t *sc; sc = device_get_softc(self); /* * only call ehci_detach() after ehci_init() */ if (sc->sc_flags & EHCI_SCFLG_DONEINIT) { ehci_detach(sc); sc->sc_flags &= ~EHCI_SCFLG_DONEINIT; } /* Disable interrupts that might have been switched on in ehci_init */ if (sc->sc_io_tag && sc->sc_io_hdl) bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, EHCI_USBINTR, 0); if (sc->sc_irq_res && sc->sc_intr_hdl) { err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); if (err) { device_printf(self, "Could not tear down irq, %d\n", err); return (err); } sc->sc_intr_hdl = NULL; } if (sc->sc_bus.bdev) { device_delete_child(self, sc->sc_bus.bdev); sc->sc_bus.bdev = NULL; } /* During module unload there are lots of children leftover */ device_delete_children(self); 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; sc->sc_io_tag = 0; sc->sc_io_hdl = 0; } return (0); } Index: projects/release-pkg/sys/dev/usb/controller/ehci_imx.c =================================================================== --- projects/release-pkg/sys/dev/usb/controller/ehci_imx.c (revision 297604) +++ projects/release-pkg/sys/dev/usb/controller/ehci_imx.c (revision 297605) @@ -1,337 +1,337 @@ /*- * Copyright (c) 2010-2012 Semihalf * Copyright (c) 2012 The FreeBSD Foundation * Copyright (c) 2013 Ian Lepore * All rights reserved. * * Portions of this software were 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. */ #include __FBSDID("$FreeBSD$"); /* * EHCI driver for Freescale i.MX SoCs which incorporate the USBOH3 controller. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #include "opt_platform.h" /* * Notes on the hardware and related FDT data seen in the wild. * * There are two sets of registers in the USBOH3 implementation; documentation * refers to them as "core" and "non-core" registers. A set of core register * exists for each OTG or EHCI device. There is a single set of non-core * registers per USBOH3, and they control aspects of operation not directly * related to the USB specs, such as whether interrupts from each of the core * devices are able to generate a SoC wakeup event. * * In the FreeBSD universe we might be inclined to describe the core and * non-core registers by using a pair of resource address/size values (two * entries in the reg property for each core). However, we have to work with * existing FDT data (which mostly comes from the linux universe), and the way * they've chosen to represent this is with an entry for a "usbmisc" device * whose reg property describes the non-core registers. The way we handle FDT * data, this means that the resources (memory-mapped register range) for the * non-core registers belongs to a device other than the echi devices. * * At the moment we have no need to access the non-core registers, so all of * this amounts to documenting what's known. The following compat strings have * been seen in existing FDT data: * - "fsl,imx25-usbmisc" * - "fsl,imx51-usbmisc"; * - "fsl,imx6q-usbmisc"; * * In addition to the single usbmisc device, the existing FDT data defines a * separate device for each of the OTG or EHCI cores within the USBOH3. Each of * those devices has a set of core registers described by the reg property. * * The core registers for each of the four cores in the USBOH3 are divided into * two parts: a set of imx-specific registers at an offset of 0 from the * beginning of the register range, and the standard USB (EHCI or OTG) registers * at an offset of 0x100 from the beginning of the register range. The FreeBSD * way of dealing with this might be to map out two ranges in the reg property, * but that's not what the alternate universe has done. To work with existing * FDT data, we acquire the resource that maps all the core registers, then use * bus_space_subregion() to create another resource that maps just the standard * USB registers, which we provide to the standard USB code in the ehci_softc. * * The following compat strings have been seen for the OTG and EHCI cores. The * FDT compat table in this driver contains all these strings, but as of this * writing, not all of these SoCs have been tested with the driver. The fact * that imx27 is common to all of them gives some hope that the driver will work * on all these SoCs. * - "fsl,imx23-usb", "fsl,imx27-usb"; * - "fsl,imx25-usb", "fsl,imx27-usb"; * - "fsl,imx28-usb", "fsl,imx27-usb"; * - "fsl,imx51-usb", "fsl,imx27-usb"; * - "fsl,imx53-usb", "fsl,imx27-usb"; * - "fsl,imx6q-usb", "fsl,imx27-usb"; * * The FDT data for some SoCs contains the following properties, which we don't * currently do anything with: * - fsl,usbmisc = <&usbmisc 0>; * - fsl,usbphy = <&usbphy0>; * * Some imx SoCs have FDT data related to USB PHY, some don't. We have separate * usbphy drivers where needed; this data is mentioned here just to keep all the * imx-FDT-usb-related info in one place. Here are the usbphy compat strings * known to exist: * - "nop-usbphy" * - "usb-nop-xceiv"; * - "fsl,imx23-usbphy" * - "fsl,imx28-usbphy", "fsl,imx23-usbphy"; * - "fsl,imx6q-usbphy", "fsl,imx23-usbphy"; * */ static struct ofw_compat_data compat_data[] = { {"fsl,imx6q-usb", 1}, {"fsl,imx53-usb", 1}, {"fsl,imx51-usb", 1}, {"fsl,imx28-usb", 1}, {"fsl,imx27-usb", 1}, {"fsl,imx25-usb", 1}, {"fsl,imx23-usb", 1}, {NULL, 0}, }; /* * Each EHCI device in the SoC has some SoC-specific per-device registers at an * offset of 0, then the standard EHCI registers begin at an offset of 0x100. */ #define IMX_EHCI_REG_OFF 0x100 #define IMX_EHCI_REG_SIZE 0x100 struct imx_ehci_softc { ehci_softc_t ehci_softc; struct resource *ehci_mem_res; /* EHCI core regs. */ struct resource *ehci_irq_res; /* EHCI core IRQ. */ }; static int imx_ehci_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 integrated USB controller"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int imx_ehci_detach(device_t dev) { struct imx_ehci_softc *sc; ehci_softc_t *esc; sc = device_get_softc(dev); esc = &sc->ehci_softc; if (esc->sc_bus.bdev != NULL) device_delete_child(dev, esc->sc_bus.bdev); if (esc->sc_flags & EHCI_SCFLG_DONEINIT) ehci_detach(esc); if (esc->sc_intr_hdl != NULL) bus_teardown_intr(dev, esc->sc_irq_res, esc->sc_intr_hdl); if (sc->ehci_irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->ehci_irq_res); if (sc->ehci_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->ehci_mem_res); usb_bus_mem_free_all(&esc->sc_bus, &ehci_iterate_hw_softc); /* During module unload there are lots of children leftover */ device_delete_children(dev); return (0); } static int imx_ehci_attach(device_t dev) { struct imx_ehci_softc *sc; ehci_softc_t *esc; int err, rid; sc = device_get_softc(dev); esc = &sc->ehci_softc; err = 0; /* Allocate bus_space resources. */ rid = 0; sc->ehci_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->ehci_mem_res == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); err = ENXIO; goto out; } rid = 0; sc->ehci_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->ehci_irq_res == NULL) { device_printf(dev, "Cannot allocate IRQ resources\n"); err = ENXIO; goto out; } esc->sc_io_tag = rman_get_bustag(sc->ehci_mem_res); esc->sc_bus.parent = dev; esc->sc_bus.devices = esc->sc_devices; esc->sc_bus.devices_max = EHCI_MAX_DEVICES; esc->sc_bus.dma_bits = 32; /* allocate all DMA memory */ if (usb_bus_mem_alloc_all(&esc->sc_bus, USB_GET_DMA_TAG(dev), &ehci_iterate_hw_softc) != 0) { device_printf(dev, "usb_bus_mem_alloc_all() failed\n"); err = ENOMEM; goto out; } /* * Set handle to USB related registers subregion used by * generic EHCI driver. */ err = bus_space_subregion(esc->sc_io_tag, rman_get_bushandle(sc->ehci_mem_res), IMX_EHCI_REG_OFF, IMX_EHCI_REG_SIZE, &esc->sc_io_hdl); if (err != 0) { device_printf(dev, "bus_space_subregion() failed\n"); err = ENXIO; goto out; } /* Setup interrupt handler. */ - err = bus_setup_intr(dev, sc->ehci_irq_res, INTR_TYPE_BIO, NULL, - (driver_intr_t *)ehci_interrupt, esc, &esc->sc_intr_hdl); + err = bus_setup_intr(dev, sc->ehci_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)ehci_interrupt, esc, &esc->sc_intr_hdl); if (err != 0) { device_printf(dev, "Could not setup IRQ\n"); goto out; } /* Turn on clocks. */ imx_ccm_usb_enable(dev); /* Add USB bus device. */ esc->sc_bus.bdev = device_add_child(dev, "usbus", -1); if (esc->sc_bus.bdev == NULL) { device_printf(dev, "Could not add USB device\n"); goto out; } device_set_ivars(esc->sc_bus.bdev, &esc->sc_bus); esc->sc_id_vendor = USB_VENDOR_FREESCALE; strlcpy(esc->sc_vendor, "Freescale", sizeof(esc->sc_vendor)); /* Set flags that affect ehci_init() behavior. */ esc->sc_flags |= EHCI_SCFLG_DONTRESET | EHCI_SCFLG_NORESTERM; err = ehci_init(esc); if (err != 0) { device_printf(dev, "USB init failed, usb_err_t=%d\n", err); goto out; } esc->sc_flags |= EHCI_SCFLG_DONEINIT; /* Probe the bus. */ err = device_probe_and_attach(esc->sc_bus.bdev); if (err != 0) { device_printf(dev, "device_probe_and_attach() failed\n"); goto out; } err = 0; out: if (err != 0) imx_ehci_detach(dev); return (err); } static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, imx_ehci_probe), DEVMETHOD(device_attach, imx_ehci_attach), DEVMETHOD(device_detach, imx_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD_END }; static driver_t ehci_driver = { "ehci", ehci_methods, sizeof(struct imx_ehci_softc) }; static devclass_t ehci_devclass; DRIVER_MODULE(ehci, simplebus, ehci_driver, ehci_devclass, 0, 0); MODULE_DEPEND(ehci, usb, 1, 1, 1); Index: projects/release-pkg/sys/dev/usb/controller/musb_otg.c =================================================================== --- projects/release-pkg/sys/dev/usb/controller/musb_otg.c (revision 297604) +++ projects/release-pkg/sys/dev/usb/controller/musb_otg.c (revision 297605) @@ -1,4256 +1,4255 @@ /* $FreeBSD$ */ /*- * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Thanks to Mentor Graphics for providing a reference driver for this USB chip * at their homepage. */ /* * This file contains the driver for the Mentor Graphics Inventra USB * 2.0 High Speed Dual-Role controller. * - * NOTE: The current implementation only supports Device Side Mode! */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR musbotgdebug #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #define MUSBOTG_INTR_ENDPT 1 #define MUSBOTG_BUS2SC(bus) \ ((struct musbotg_softc *)(((uint8_t *)(bus)) - \ USB_P2U(&(((struct musbotg_softc *)0)->sc_bus)))) #define MUSBOTG_PC2SC(pc) \ MUSBOTG_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus) #ifdef USB_DEBUG static int musbotgdebug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, musbotg, CTLFLAG_RW, 0, "USB musbotg"); SYSCTL_INT(_hw_usb_musbotg, OID_AUTO, debug, CTLFLAG_RWTUN, &musbotgdebug, 0, "Debug level"); #endif #define MAX_NAK_TO 16 /* prototypes */ static const struct usb_bus_methods musbotg_bus_methods; static const struct usb_pipe_methods musbotg_device_bulk_methods; static const struct usb_pipe_methods musbotg_device_ctrl_methods; static const struct usb_pipe_methods musbotg_device_intr_methods; static const struct usb_pipe_methods musbotg_device_isoc_methods; /* Control transfers: Device mode */ static musbotg_cmd_t musbotg_dev_ctrl_setup_rx; static musbotg_cmd_t musbotg_dev_ctrl_data_rx; static musbotg_cmd_t musbotg_dev_ctrl_data_tx; static musbotg_cmd_t musbotg_dev_ctrl_status; /* Control transfers: Host mode */ static musbotg_cmd_t musbotg_host_ctrl_setup_tx; static musbotg_cmd_t musbotg_host_ctrl_data_rx; static musbotg_cmd_t musbotg_host_ctrl_data_tx; static musbotg_cmd_t musbotg_host_ctrl_status_rx; static musbotg_cmd_t musbotg_host_ctrl_status_tx; /* Bulk, Interrupt, Isochronous: Device mode */ static musbotg_cmd_t musbotg_dev_data_rx; static musbotg_cmd_t musbotg_dev_data_tx; /* Bulk, Interrupt, Isochronous: Host mode */ static musbotg_cmd_t musbotg_host_data_rx; static musbotg_cmd_t musbotg_host_data_tx; static void musbotg_device_done(struct usb_xfer *, usb_error_t); static void musbotg_do_poll(struct usb_bus *); static void musbotg_standard_done(struct usb_xfer *); static void musbotg_interrupt_poll(struct musbotg_softc *); static void musbotg_root_intr(struct musbotg_softc *); static int musbotg_channel_alloc(struct musbotg_softc *, struct musbotg_td *td, uint8_t); static void musbotg_channel_free(struct musbotg_softc *, struct musbotg_td *td); static void musbotg_ep_int_set(struct musbotg_softc *sc, int channel, int on); /* * Here is a configuration that the chip supports. */ static const struct usb_hw_ep_profile musbotg_ep_profile[1] = { [0] = { .max_in_frame_size = 64,/* fixed */ .max_out_frame_size = 64, /* fixed */ .is_simplex = 1, .support_control = 1, } }; static int musbotg_channel_alloc(struct musbotg_softc *sc, struct musbotg_td *td, uint8_t is_tx) { int ch; int ep; ep = td->ep_no; /* In device mode each EP got its own channel */ if (sc->sc_mode == MUSB2_DEVICE_MODE) { musbotg_ep_int_set(sc, ep, 1); return (ep); } /* * All control transactions go through EP0 */ if (ep == 0) { if (sc->sc_channel_mask & (1 << 0)) return (-1); sc->sc_channel_mask |= (1 << 0); musbotg_ep_int_set(sc, ep, 1); return (0); } for (ch = sc->sc_ep_max; ch != 0; ch--) { if (sc->sc_channel_mask & (1 << ch)) continue; /* check FIFO size requirement */ if (is_tx) { if (td->max_frame_size > sc->sc_hw_ep_profile[ch].max_in_frame_size) continue; } else { if (td->max_frame_size > sc->sc_hw_ep_profile[ch].max_out_frame_size) continue; } sc->sc_channel_mask |= (1 << ch); musbotg_ep_int_set(sc, ch, 1); return (ch); } DPRINTFN(-1, "No available channels. Mask: %04x\n", sc->sc_channel_mask); return (-1); } static void musbotg_channel_free(struct musbotg_softc *sc, struct musbotg_td *td) { DPRINTFN(1, "ep_no=%d\n", td->channel); if (sc->sc_mode == MUSB2_DEVICE_MODE) return; if (td == NULL) return; if (td->channel == -1) return; musbotg_ep_int_set(sc, td->channel, 0); sc->sc_channel_mask &= ~(1 << td->channel); td->channel = -1; } static void musbotg_get_hw_ep_profile(struct usb_device *udev, const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) { struct musbotg_softc *sc; sc = MUSBOTG_BUS2SC(udev->bus); if (ep_addr == 0) { /* control endpoint */ *ppf = musbotg_ep_profile; } else if (ep_addr <= sc->sc_ep_max) { /* other endpoints */ *ppf = sc->sc_hw_ep_profile + ep_addr; } else { *ppf = NULL; } } static void musbotg_clocks_on(struct musbotg_softc *sc) { if (sc->sc_flags.clocks_off && sc->sc_flags.port_powered) { DPRINTFN(4, "\n"); if (sc->sc_clocks_on) { (sc->sc_clocks_on) (sc->sc_clocks_arg); } sc->sc_flags.clocks_off = 0; /* XXX enable Transceiver */ } } static void musbotg_clocks_off(struct musbotg_softc *sc) { if (!sc->sc_flags.clocks_off) { DPRINTFN(4, "\n"); /* XXX disable Transceiver */ if (sc->sc_clocks_off) { (sc->sc_clocks_off) (sc->sc_clocks_arg); } sc->sc_flags.clocks_off = 1; } } static void musbotg_pull_common(struct musbotg_softc *sc, uint8_t on) { uint8_t temp; temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); if (on) temp |= MUSB2_MASK_SOFTC; else temp &= ~MUSB2_MASK_SOFTC; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); } static void musbotg_pull_up(struct musbotg_softc *sc) { /* pullup D+, if possible */ if (!sc->sc_flags.d_pulled_up && sc->sc_flags.port_powered) { sc->sc_flags.d_pulled_up = 1; musbotg_pull_common(sc, 1); } } static void musbotg_pull_down(struct musbotg_softc *sc) { /* pulldown D+, if possible */ if (sc->sc_flags.d_pulled_up) { sc->sc_flags.d_pulled_up = 0; musbotg_pull_common(sc, 0); } } static void musbotg_suspend_host(struct musbotg_softc *sc) { uint8_t temp; if (sc->sc_flags.status_suspend) { return; } temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); temp |= MUSB2_MASK_SUSPMODE; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); sc->sc_flags.status_suspend = 1; } static void musbotg_wakeup_host(struct musbotg_softc *sc) { uint8_t temp; if (!(sc->sc_flags.status_suspend)) { return; } temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); temp &= ~MUSB2_MASK_SUSPMODE; temp |= MUSB2_MASK_RESUME; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); /* wait 20 milliseconds */ /* Wait for reset to complete. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50); temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); temp &= ~MUSB2_MASK_RESUME; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); sc->sc_flags.status_suspend = 0; } static void musbotg_wakeup_peer(struct musbotg_softc *sc) { uint8_t temp; if (!(sc->sc_flags.status_suspend)) { return; } temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); temp |= MUSB2_MASK_RESUME; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); /* wait 8 milliseconds */ /* Wait for reset to complete. */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); temp &= ~MUSB2_MASK_RESUME; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); } static void musbotg_set_address(struct musbotg_softc *sc, uint8_t addr) { DPRINTFN(4, "addr=%d\n", addr); addr &= 0x7F; MUSB2_WRITE_1(sc, MUSB2_REG_FADDR, addr); } static uint8_t musbotg_dev_ctrl_setup_rx(struct musbotg_td *td) { struct musbotg_softc *sc; struct usb_device_request req; uint16_t count; uint8_t csr; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 0); /* EP0 is busy, wait */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); /* * NOTE: If DATAEND is set we should not call the * callback, hence the status stage is not complete. */ if (csr & MUSB2_MASK_CSR0L_DATAEND) { /* do not stall at this point */ td->did_stall = 1; /* wait for interrupt */ DPRINTFN(1, "CSR0 DATAEND\n"); goto not_complete; } if (csr & MUSB2_MASK_CSR0L_SENTSTALL) { /* clear SENTSTALL */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); /* get latest status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); /* update EP0 state */ sc->sc_ep0_busy = 0; } if (csr & MUSB2_MASK_CSR0L_SETUPEND) { /* clear SETUPEND */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_SETUPEND_CLR); /* get latest status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); /* update EP0 state */ sc->sc_ep0_busy = 0; } if (sc->sc_ep0_busy) { DPRINTFN(1, "EP0 BUSY\n"); goto not_complete; } if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) { goto not_complete; } /* get the packet byte count */ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); /* verify data length */ if (count != td->remainder) { DPRINTFN(1, "Invalid SETUP packet " "length, %d bytes\n", count); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_RXPKTRDY_CLR); /* don't clear stall */ td->did_stall = 1; goto not_complete; } if (count != sizeof(req)) { DPRINTFN(1, "Unsupported SETUP packet " "length, %d bytes\n", count); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_RXPKTRDY_CLR); /* don't clear stall */ td->did_stall = 1; goto not_complete; } /* clear did stall flag */ td->did_stall = 0; /* receive data */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), (void *)&req, sizeof(req)); /* copy data into real buffer */ usbd_copy_in(td->pc, 0, &req, sizeof(req)); td->offset = sizeof(req); td->remainder = 0; /* set pending command */ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR; /* we need set stall or dataend after this */ sc->sc_ep0_busy = 1; /* sneak peek the set address */ if ((req.bmRequestType == UT_WRITE_DEVICE) && (req.bRequest == UR_SET_ADDRESS)) { sc->sc_dv_addr = req.wValue[0] & 0x7F; } else { sc->sc_dv_addr = 0xFF; } musbotg_channel_free(sc, td); return (0); /* complete */ not_complete: /* abort any ongoing transfer */ if (!td->did_stall) { DPRINTFN(4, "stalling\n"); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_SENDSTALL); td->did_stall = 1; } return (1); /* not complete */ } static uint8_t musbotg_host_ctrl_setup_tx(struct musbotg_td *td) { struct musbotg_softc *sc; struct usb_device_request req; uint8_t csr, csrh; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 1); /* EP0 is busy, wait */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); /* Not ready yet yet */ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) return (1); /* Failed */ if (csr & (MUSB2_MASK_CSR0L_RXSTALL | MUSB2_MASK_CSR0L_ERROR)) { /* Clear status bit */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); DPRINTFN(1, "error bit set, csr=0x%02x\n", csr); td->error = 1; } if (csr & MUSB2_MASK_CSR0L_NAKTIMO) { DPRINTFN(1, "NAK timeout\n"); if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) { csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH); csrh |= MUSB2_MASK_CSR0H_FFLUSH; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) { csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH); csrh |= MUSB2_MASK_CSR0H_FFLUSH; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); } } csr &= ~MUSB2_MASK_CSR0L_NAKTIMO; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); td->error = 1; } if (td->error) { musbotg_channel_free(sc, td); return (0); } /* Fifo is not empty and there is no NAK timeout */ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) return (1); /* check if we are complete */ if (td->remainder == 0) { /* we are complete */ musbotg_channel_free(sc, td); return (0); } /* copy data into real buffer */ usbd_copy_out(td->pc, 0, &req, sizeof(req)); /* send data */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), (void *)&req, sizeof(req)); /* update offset and remainder */ td->offset += sizeof(req); td->remainder -= sizeof(req); MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, MAX_NAK_TO); MUSB2_WRITE_1(sc, MUSB2_REG_TXFADDR(0), td->dev_addr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHADDR(0), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHUBPORT(0), td->hport); MUSB2_WRITE_1(sc, MUSB2_REG_TXTI, td->transfer_type); /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_TXPKTRDY | MUSB2_MASK_CSR0L_SETUPPKT); /* Just to be consistent, not used above */ td->transaction_started = 1; return (1); /* in progress */ } /* Control endpoint only data handling functions (RX/TX/SYNC) */ static uint8_t musbotg_dev_ctrl_data_rx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr; uint8_t got_short; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* check if a command is pending */ if (sc->sc_ep0_cmd) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd); sc->sc_ep0_cmd = 0; } /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); got_short = 0; if (csr & (MUSB2_MASK_CSR0L_SETUPEND | MUSB2_MASK_CSR0L_SENTSTALL)) { if (td->remainder == 0) { /* * We are actually complete and have * received the next SETUP */ DPRINTFN(4, "faking complete\n"); return (0); /* complete */ } /* * USB Host Aborted the transfer. */ td->error = 1; return (0); /* complete */ } if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) { return (1); /* not complete */ } /* get the packet byte count */ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); /* verify the packet byte count */ if (count != td->max_frame_size) { if (count < td->max_frame_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error = 1; return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error = 1; return (0); /* we are complete */ } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { temp = count & ~3; if (temp) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), (void *)(&sc->sc_bounce_buf[count / 4]), temp); } usbd_copy_in(td->pc, td->offset, sc->sc_bounce_buf, count); /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* receive data */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR; return (0); } /* else need to receive a zero length packet */ } /* write command - need more data */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_RXPKTRDY_CLR); return (1); /* not complete */ } static uint8_t musbotg_dev_ctrl_data_tx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* check if a command is pending */ if (sc->sc_ep0_cmd) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd); sc->sc_ep0_cmd = 0; } /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); if (csr & (MUSB2_MASK_CSR0L_SETUPEND | MUSB2_MASK_CSR0L_SENTSTALL)) { /* * The current transfer was aborted * by the USB Host */ td->error = 1; return (0); /* complete */ } if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) { return (1); /* not complete */ } count = td->max_frame_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buf, count); temp = count & ~3; if (temp) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), ((void *)&sc->sc_bounce_buf[count / 4]), temp); } /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* transmit data */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) { sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_TXPKTRDY; return (0); /* complete */ } /* else we need to transmit a short packet */ } /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_TXPKTRDY); return (1); /* not complete */ } static uint8_t musbotg_host_ctrl_data_rx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr; uint8_t got_short; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 0); /* EP0 is busy, wait */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); got_short = 0; if (!td->transaction_started) { td->transaction_started = 1; MUSB2_WRITE_1(sc, MUSB2_REG_RXNAKLIMIT, MAX_NAK_TO); MUSB2_WRITE_1(sc, MUSB2_REG_RXFADDR(0), td->dev_addr); MUSB2_WRITE_1(sc, MUSB2_REG_RXHADDR(0), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_RXHUBPORT(0), td->hport); MUSB2_WRITE_1(sc, MUSB2_REG_RXTI, td->transfer_type); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_REQPKT); return (1); } if (csr & MUSB2_MASK_CSR0L_NAKTIMO) { csr &= ~MUSB2_MASK_CSR0L_REQPKT; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); csr &= ~MUSB2_MASK_CSR0L_NAKTIMO; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); td->error = 1; } /* Failed */ if (csr & (MUSB2_MASK_CSR0L_RXSTALL | MUSB2_MASK_CSR0L_ERROR)) { /* Clear status bit */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); DPRINTFN(1, "error bit set, csr=0x%02x\n", csr); td->error = 1; } if (td->error) { musbotg_channel_free(sc, td); return (0); /* we are complete */ } if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) return (1); /* not yet */ /* get the packet byte count */ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); /* verify the packet byte count */ if (count != td->max_frame_size) { if (count < td->max_frame_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error = 1; musbotg_channel_free(sc, td); return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error = 1; musbotg_channel_free(sc, td); return (0); /* we are complete */ } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { temp = count & ~3; if (temp) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), (void *)(&sc->sc_bounce_buf[count / 4]), temp); } usbd_copy_in(td->pc, td->offset, sc->sc_bounce_buf, count); /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* receive data */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } csr &= ~MUSB2_MASK_CSR0L_RXPKTRDY; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ musbotg_channel_free(sc, td); return (0); } /* else need to receive a zero length packet */ } td->transaction_started = 1; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_REQPKT); return (1); /* not complete */ } static uint8_t musbotg_host_ctrl_data_tx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr, csrh; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 1); /* No free EPs */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); if (csr & (MUSB2_MASK_CSR0L_RXSTALL | MUSB2_MASK_CSR0L_ERROR)) { /* clear status bits */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); td->error = 1; } if (csr & MUSB2_MASK_CSR0L_NAKTIMO ) { if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) { csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH); csrh |= MUSB2_MASK_CSR0H_FFLUSH; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) { csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH); csrh |= MUSB2_MASK_CSR0H_FFLUSH; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); } } csr &= ~MUSB2_MASK_CSR0L_NAKTIMO; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); td->error = 1; } if (td->error) { musbotg_channel_free(sc, td); return (0); /* complete */ } /* * Wait while FIFO is empty. * Do not flush it because it will cause transactions * with size more then packet size. It might upset * some devices */ if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) return (1); /* Packet still being processed */ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) return (1); if (td->transaction_started) { /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) { musbotg_channel_free(sc, td); return (0); /* complete */ } /* else we need to transmit a short packet */ } /* We're not complete - more transactions required */ td->transaction_started = 0; } /* check for short packet */ count = td->max_frame_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buf, count); temp = count & ~3; if (temp) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), ((void *)&sc->sc_bounce_buf[count / 4]), temp); } /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* transmit data */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* Function address */ MUSB2_WRITE_1(sc, MUSB2_REG_TXFADDR(0), td->dev_addr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHADDR(0), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHUBPORT(0), td->hport); MUSB2_WRITE_1(sc, MUSB2_REG_TXTI, td->transfer_type); /* TX NAK timeout */ MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, MAX_NAK_TO); /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_TXPKTRDY); td->transaction_started = 1; return (1); /* not complete */ } static uint8_t musbotg_dev_ctrl_status(struct musbotg_td *td) { struct musbotg_softc *sc; uint8_t csr; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); if (sc->sc_ep0_busy) { sc->sc_ep0_busy = 0; sc->sc_ep0_cmd |= MUSB2_MASK_CSR0L_DATAEND; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd); sc->sc_ep0_cmd = 0; } /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); if (csr & MUSB2_MASK_CSR0L_DATAEND) { /* wait for interrupt */ return (1); /* not complete */ } if (sc->sc_dv_addr != 0xFF) { /* write function address */ musbotg_set_address(sc, sc->sc_dv_addr); } musbotg_channel_free(sc, td); return (0); /* complete */ } static uint8_t musbotg_host_ctrl_status_rx(struct musbotg_td *td) { struct musbotg_softc *sc; uint8_t csr, csrh; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 0); /* EP0 is busy, wait */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); if (!td->transaction_started) { MUSB2_WRITE_1(sc, MUSB2_REG_RXFADDR(0), td->dev_addr); MUSB2_WRITE_1(sc, MUSB2_REG_RXHADDR(0), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_RXHUBPORT(0), td->hport); MUSB2_WRITE_1(sc, MUSB2_REG_RXTI, td->transfer_type); /* RX NAK timeout */ MUSB2_WRITE_1(sc, MUSB2_REG_RXNAKLIMIT, MAX_NAK_TO); td->transaction_started = 1; /* Disable PING */ csrh = MUSB2_READ_1(sc, MUSB2_REG_RXCSRH); csrh |= MUSB2_MASK_CSR0H_PING_DIS; MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, csrh); /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_STATUSPKT | MUSB2_MASK_CSR0L_REQPKT); return (1); /* Just started */ } csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "IN STATUS csr=0x%02x\n", csr); if (csr & MUSB2_MASK_CSR0L_RXPKTRDY) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_RXPKTRDY_CLR); musbotg_channel_free(sc, td); return (0); /* complete */ } if (csr & MUSB2_MASK_CSR0L_NAKTIMO) { csr &= ~ (MUSB2_MASK_CSR0L_STATUSPKT | MUSB2_MASK_CSR0L_REQPKT); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); csr &= ~MUSB2_MASK_CSR0L_NAKTIMO; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); td->error = 1; } /* Failed */ if (csr & (MUSB2_MASK_CSR0L_RXSTALL | MUSB2_MASK_CSR0L_ERROR)) { /* Clear status bit */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); DPRINTFN(1, "error bit set, csr=0x%02x\n", csr); td->error = 1; } if (td->error) { musbotg_channel_free(sc, td); return (0); } return (1); /* Not ready yet */ } static uint8_t musbotg_host_ctrl_status_tx(struct musbotg_td *td) { struct musbotg_softc *sc; uint8_t csr; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 1); /* EP0 is busy, wait */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d/%d [%d@%d.%d/%02x]\n", td->channel, td->transaction_started, td->dev_addr,td->haddr,td->hport, td->transfer_type); /* select endpoint 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); /* Not yet */ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) return (1); /* Failed */ if (csr & (MUSB2_MASK_CSR0L_RXSTALL | MUSB2_MASK_CSR0L_ERROR)) { /* Clear status bit */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); DPRINTFN(1, "error bit set, csr=0x%02x\n", csr); td->error = 1; musbotg_channel_free(sc, td); return (0); /* complete */ } if (td->transaction_started) { musbotg_channel_free(sc, td); return (0); /* complete */ } MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, MUSB2_MASK_CSR0H_PING_DIS); MUSB2_WRITE_1(sc, MUSB2_REG_TXFADDR(0), td->dev_addr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHADDR(0), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHUBPORT(0), td->hport); MUSB2_WRITE_1(sc, MUSB2_REG_TXTI, td->transfer_type); /* TX NAK timeout */ MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, MAX_NAK_TO); td->transaction_started = 1; /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSR0L_STATUSPKT | MUSB2_MASK_CSR0L_TXPKTRDY); return (1); /* wait for interrupt */ } static uint8_t musbotg_dev_data_rx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr; uint8_t to; uint8_t got_short; to = 8; /* don't loop forever! */ got_short = 0; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 0); /* EP0 is busy, wait */ if (td->channel == -1) return (1); /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->channel); repeat: /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); /* clear overrun */ if (csr & MUSB2_MASK_CSRL_RXOVERRUN) { /* make sure we don't clear "RXPKTRDY" */ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, MUSB2_MASK_CSRL_RXPKTRDY); } /* check status */ if (!(csr & MUSB2_MASK_CSRL_RXPKTRDY)) return (1); /* not complete */ /* get the packet byte count */ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); DPRINTFN(4, "count=0x%04x\n", count); /* * Check for short or invalid packet: */ if (count != td->max_frame_size) { if (count < td->max_frame_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error = 1; musbotg_channel_free(sc, td); return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error = 1; musbotg_channel_free(sc, td); return (0); /* we are complete */ } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { temp = count & ~3; if (temp) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), ((void *)&sc->sc_bounce_buf[count / 4]), temp); } usbd_copy_in(td->pc, td->offset, sc->sc_bounce_buf, count); /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* receive data */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* clear status bits */ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ musbotg_channel_free(sc, td); return (0); } /* else need to receive a zero length packet */ } if (--to) { goto repeat; } return (1); /* not complete */ } static uint8_t musbotg_dev_data_tx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr; uint8_t to; to = 8; /* don't loop forever! */ /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 1); /* EP0 is busy, wait */ if (td->channel == -1) return (1); /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->channel); repeat: /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); if (csr & (MUSB2_MASK_CSRL_TXINCOMP | MUSB2_MASK_CSRL_TXUNDERRUN)) { /* clear status bits */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); } if (csr & MUSB2_MASK_CSRL_TXPKTRDY) { return (1); /* not complete */ } /* check for short packet */ count = td->max_frame_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buf, count); temp = count & ~3; if (temp) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), ((void *)&sc->sc_bounce_buf[count / 4]), temp); } /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* transmit data */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* Max packet size */ MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, td->reg_max_packet); /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSRL_TXPKTRDY); /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) { musbotg_channel_free(sc, td); return (0); /* complete */ } /* else we need to transmit a short packet */ } if (--to) { goto repeat; } return (1); /* not complete */ } static uint8_t musbotg_host_data_rx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr, csrh; uint8_t to; uint8_t got_short; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 0); /* No free EPs */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); to = 8; /* don't loop forever! */ got_short = 0; /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->channel); repeat: /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); if (!td->transaction_started) { /* Function address */ MUSB2_WRITE_1(sc, MUSB2_REG_RXFADDR(td->channel), td->dev_addr); /* SPLIT transaction */ MUSB2_WRITE_1(sc, MUSB2_REG_RXHADDR(td->channel), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_RXHUBPORT(td->channel), td->hport); /* RX NAK timeout */ if (td->transfer_type & MUSB2_MASK_TI_PROTO_ISOC) MUSB2_WRITE_1(sc, MUSB2_REG_RXNAKLIMIT, 0); else MUSB2_WRITE_1(sc, MUSB2_REG_RXNAKLIMIT, MAX_NAK_TO); /* Protocol, speed, device endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_RXTI, td->transfer_type); /* Max packet size */ MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, td->reg_max_packet); /* Data Toggle */ csrh = MUSB2_READ_1(sc, MUSB2_REG_RXCSRH); DPRINTFN(4, "csrh=0x%02x\n", csrh); csrh |= MUSB2_MASK_CSRH_RXDT_WREN; if (td->toggle) csrh |= MUSB2_MASK_CSRH_RXDT_VAL; else csrh &= ~MUSB2_MASK_CSRH_RXDT_VAL; /* Set data toggle */ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, csrh); /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, MUSB2_MASK_CSRL_RXREQPKT); td->transaction_started = 1; return (1); } /* clear NAK timeout */ if (csr & MUSB2_MASK_CSRL_RXNAKTO) { DPRINTFN(4, "NAK Timeout\n"); if (csr & MUSB2_MASK_CSRL_RXREQPKT) { csr &= ~MUSB2_MASK_CSRL_RXREQPKT; MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, csr); csr &= ~MUSB2_MASK_CSRL_RXNAKTO; MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, csr); } td->error = 1; } if (csr & MUSB2_MASK_CSRL_RXERROR) { DPRINTFN(4, "RXERROR\n"); td->error = 1; } if (csr & MUSB2_MASK_CSRL_RXSTALL) { DPRINTFN(4, "RXSTALL\n"); td->error = 1; } if (td->error) { musbotg_channel_free(sc, td); return (0); /* we are complete */ } if (!(csr & MUSB2_MASK_CSRL_RXPKTRDY)) { /* No data available yet */ return (1); } td->toggle ^= 1; /* get the packet byte count */ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); DPRINTFN(4, "count=0x%04x\n", count); /* * Check for short or invalid packet: */ if (count != td->max_frame_size) { if (count < td->max_frame_size) { /* we have a short packet */ td->short_pkt = 1; got_short = 1; } else { /* invalid USB packet */ td->error = 1; musbotg_channel_free(sc, td); return (0); /* we are complete */ } } /* verify the packet byte count */ if (count > td->remainder) { /* invalid USB packet */ td->error = 1; musbotg_channel_free(sc, td); return (0); /* we are complete */ } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { temp = count & ~3; if (temp) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), ((void *)&sc->sc_bounce_buf[count / 4]), temp); } usbd_copy_in(td->pc, td->offset, sc->sc_bounce_buf, count); /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* receive data 4 bytes at a time */ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* receive data */ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* clear status bits */ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); /* check if we are complete */ if ((td->remainder == 0) || got_short) { if (td->short_pkt) { /* we are complete */ musbotg_channel_free(sc, td); return (0); } /* else need to receive a zero length packet */ } /* Reset transaction state and restart */ td->transaction_started = 0; if (--to) goto repeat; return (1); /* not complete */ } static uint8_t musbotg_host_data_tx(struct musbotg_td *td) { struct usb_page_search buf_res; struct musbotg_softc *sc; uint16_t count; uint8_t csr, csrh; /* get pointer to softc */ sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) td->channel = musbotg_channel_alloc(sc, td, 1); /* No free EPs */ if (td->channel == -1) return (1); DPRINTFN(1, "ep_no=%d\n", td->channel); /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->channel); /* read out FIFO status */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); DPRINTFN(4, "csr=0x%02x\n", csr); if (csr & (MUSB2_MASK_CSRL_TXSTALLED | MUSB2_MASK_CSRL_TXERROR)) { /* clear status bits */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); td->error = 1; musbotg_channel_free(sc, td); return (0); /* complete */ } if (csr & MUSB2_MASK_CSRL_TXNAKTO) { /* * Flush TX FIFO before clearing NAK TO */ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) { csr |= MUSB2_MASK_CSRL_TXFFLUSH; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) { csr |= MUSB2_MASK_CSRL_TXFFLUSH; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); } } csr &= ~MUSB2_MASK_CSRL_TXNAKTO; MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr); td->error = 1; musbotg_channel_free(sc, td); return (0); /* complete */ } /* * Wait while FIFO is empty. * Do not flush it because it will cause transactions * with size more then packet size. It might upset * some devices */ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) return (1); /* Packet still being processed */ if (csr & MUSB2_MASK_CSRL_TXPKTRDY) return (1); if (td->transaction_started) { /* check remainder */ if (td->remainder == 0) { if (td->short_pkt) { musbotg_channel_free(sc, td); return (0); /* complete */ } /* else we need to transmit a short packet */ } /* We're not complete - more transactions required */ td->transaction_started = 0; } /* check for short packet */ count = td->max_frame_size; if (td->remainder < count) { /* we have a short packet */ td->short_pkt = 1; count = td->remainder; } while (count > 0) { uint32_t temp; usbd_get_page(td->pc, td->offset, &buf_res); /* get correct length */ if (buf_res.length > count) { buf_res.length = count; } /* check for unaligned memory address */ if (USB_P2U(buf_res.buffer) & 3) { usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buf, count); temp = count & ~3; if (temp) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), sc->sc_bounce_buf, temp / 4); } temp = count & 3; if (temp) { /* receive data 1 byte at a time */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), ((void *)&sc->sc_bounce_buf[count / 4]), temp); } /* update offset and remainder */ td->offset += count; td->remainder -= count; break; } /* check if we can optimise */ if (buf_res.length >= 4) { /* transmit data 4 bytes at a time */ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length / 4); temp = buf_res.length & ~3; /* update counters */ count -= temp; td->offset += temp; td->remainder -= temp; continue; } /* transmit data */ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel), buf_res.buffer, buf_res.length); /* update counters */ count -= buf_res.length; td->offset += buf_res.length; td->remainder -= buf_res.length; } /* Function address */ MUSB2_WRITE_1(sc, MUSB2_REG_TXFADDR(td->channel), td->dev_addr); /* SPLIT transaction */ MUSB2_WRITE_1(sc, MUSB2_REG_TXHADDR(td->channel), td->haddr); MUSB2_WRITE_1(sc, MUSB2_REG_TXHUBPORT(td->channel), td->hport); /* TX NAK timeout */ if (td->transfer_type & MUSB2_MASK_TI_PROTO_ISOC) MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, 0); else MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, MAX_NAK_TO); /* Protocol, speed, device endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_TXTI, td->transfer_type); /* Max packet size */ MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, td->reg_max_packet); if (!td->transaction_started) { csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH); DPRINTFN(4, "csrh=0x%02x\n", csrh); csrh |= MUSB2_MASK_CSRH_TXDT_WREN; if (td->toggle) csrh |= MUSB2_MASK_CSRH_TXDT_VAL; else csrh &= ~MUSB2_MASK_CSRH_TXDT_VAL; /* Set data toggle */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh); } /* write command */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSRL_TXPKTRDY); /* Update Data Toggle */ td->toggle ^= 1; td->transaction_started = 1; return (1); /* not complete */ } static uint8_t musbotg_xfer_do_fifo(struct usb_xfer *xfer) { struct musbotg_softc *sc; struct musbotg_td *td; DPRINTFN(8, "\n"); sc = MUSBOTG_BUS2SC(xfer->xroot->bus); td = xfer->td_transfer_cache; while (1) { if ((td->func) (td)) { /* operation in progress */ break; } if (((void *)td) == xfer->td_transfer_last) { goto done; } if (td->error) { goto done; } else if (td->remainder > 0) { /* * We had a short transfer. If there is no alternate * next, stop processing ! */ if (!td->alt_next) { goto done; } } /* * Fetch the next transfer descriptor and transfer * some flags to the next transfer descriptor */ td = td->obj_next; xfer->td_transfer_cache = td; } return (1); /* not complete */ done: /* compute all actual lengths */ musbotg_standard_done(xfer); return (0); /* complete */ } static void musbotg_interrupt_poll(struct musbotg_softc *sc) { struct usb_xfer *xfer; repeat: TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (!musbotg_xfer_do_fifo(xfer)) { /* queue has been modified */ goto repeat; } } } void musbotg_vbus_interrupt(struct musbotg_softc *sc, uint8_t is_on) { DPRINTFN(4, "vbus = %u\n", is_on); USB_BUS_LOCK(&sc->sc_bus); if (is_on) { if (!sc->sc_flags.status_vbus) { sc->sc_flags.status_vbus = 1; /* complete root HUB interrupt endpoint */ musbotg_root_intr(sc); } } else { if (sc->sc_flags.status_vbus) { sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* complete root HUB interrupt endpoint */ musbotg_root_intr(sc); } } USB_BUS_UNLOCK(&sc->sc_bus); } void musbotg_connect_interrupt(struct musbotg_softc *sc) { USB_BUS_LOCK(&sc->sc_bus); sc->sc_flags.change_connect = 1; /* complete root HUB interrupt endpoint */ musbotg_root_intr(sc); USB_BUS_UNLOCK(&sc->sc_bus); } void musbotg_interrupt(struct musbotg_softc *sc, uint16_t rxstat, uint16_t txstat, uint8_t stat) { uint16_t rx_status; uint16_t tx_status; uint8_t usb_status; uint8_t temp; uint8_t to = 2; USB_BUS_LOCK(&sc->sc_bus); repeat: /* read all interrupt registers */ usb_status = MUSB2_READ_1(sc, MUSB2_REG_INTUSB); /* read all FIFO interrupts */ rx_status = MUSB2_READ_2(sc, MUSB2_REG_INTRX); tx_status = MUSB2_READ_2(sc, MUSB2_REG_INTTX); rx_status |= rxstat; tx_status |= txstat; usb_status |= stat; /* Clear platform flags after first time */ rxstat = 0; txstat = 0; stat = 0; /* check for any bus state change interrupts */ if (usb_status & (MUSB2_MASK_IRESET | MUSB2_MASK_IRESUME | MUSB2_MASK_ISUSP | MUSB2_MASK_ICONN | MUSB2_MASK_IDISC | MUSB2_MASK_IVBUSERR)) { DPRINTFN(4, "real bus interrupt 0x%08x\n", usb_status); if (usb_status & MUSB2_MASK_IRESET) { /* set correct state */ sc->sc_flags.status_bus_reset = 1; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; /* determine line speed */ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); if (temp & MUSB2_MASK_HSMODE) sc->sc_flags.status_high_speed = 1; else sc->sc_flags.status_high_speed = 0; /* * After reset all interrupts are on and we need to * turn them off! */ temp = MUSB2_MASK_IRESET; /* disable resume interrupt */ temp &= ~MUSB2_MASK_IRESUME; /* enable suspend interrupt */ temp |= MUSB2_MASK_ISUSP; MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp); /* disable TX and RX interrupts */ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0); MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0); } /* * If RXRSM and RXSUSP is set at the same time we interpret * that like RESUME. Resume is set when there is at least 3 * milliseconds of inactivity on the USB BUS. */ if (usb_status & MUSB2_MASK_IRESUME) { if (sc->sc_flags.status_suspend) { sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 1; temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE); /* disable resume interrupt */ temp &= ~MUSB2_MASK_IRESUME; /* enable suspend interrupt */ temp |= MUSB2_MASK_ISUSP; MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp); } } else if (usb_status & MUSB2_MASK_ISUSP) { if (!sc->sc_flags.status_suspend) { sc->sc_flags.status_suspend = 1; sc->sc_flags.change_suspend = 1; temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE); /* disable suspend interrupt */ temp &= ~MUSB2_MASK_ISUSP; /* enable resume interrupt */ temp |= MUSB2_MASK_IRESUME; MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp); } } if (usb_status & (MUSB2_MASK_ICONN | MUSB2_MASK_IDISC)) sc->sc_flags.change_connect = 1; /* * Host Mode: There is no IRESET so assume bus is * always in reset state once device is connected. */ if (sc->sc_mode == MUSB2_HOST_MODE) { /* check for VBUS error in USB host mode */ if (usb_status & MUSB2_MASK_IVBUSERR) { temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL); temp |= MUSB2_MASK_SESS; MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp); } if (usb_status & MUSB2_MASK_ICONN) sc->sc_flags.status_bus_reset = 1; if (usb_status & MUSB2_MASK_IDISC) sc->sc_flags.status_bus_reset = 0; } /* complete root HUB interrupt endpoint */ musbotg_root_intr(sc); } /* check for any endpoint interrupts */ if (rx_status || tx_status) { DPRINTFN(4, "real endpoint interrupt " "rx=0x%04x, tx=0x%04x\n", rx_status, tx_status); } /* poll one time regardless of FIFO status */ musbotg_interrupt_poll(sc); if (--to) goto repeat; USB_BUS_UNLOCK(&sc->sc_bus); } static void musbotg_setup_standard_chain_sub(struct musbotg_std_temp *temp) { struct musbotg_td *td; /* get current Transfer Descriptor */ td = temp->td_next; temp->td = td; /* prepare for next TD */ temp->td_next = td->obj_next; /* fill out the Transfer Descriptor */ td->func = temp->func; td->pc = temp->pc; td->offset = temp->offset; td->remainder = temp->len; td->error = 0; td->transaction_started = 0; td->did_stall = temp->did_stall; td->short_pkt = temp->short_pkt; td->alt_next = temp->setup_alt_next; td->channel = temp->channel; td->dev_addr = temp->dev_addr; td->haddr = temp->haddr; td->hport = temp->hport; td->transfer_type = temp->transfer_type; } static void musbotg_setup_standard_chain(struct usb_xfer *xfer) { struct musbotg_std_temp temp; struct musbotg_softc *sc; struct musbotg_td *td; uint32_t x; uint8_t ep_no; uint8_t xfer_type; enum usb_dev_speed speed; int tx; int dev_addr; DPRINTFN(8, "addr=%d endpt=%d sumlen=%d speed=%d\n", xfer->address, UE_GET_ADDR(xfer->endpointno), xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); sc = MUSBOTG_BUS2SC(xfer->xroot->bus); ep_no = (xfer->endpointno & UE_ADDR); temp.max_frame_size = xfer->max_frame_size; td = xfer->td_start[0]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; /* setup temp */ dev_addr = xfer->address; xfer_type = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE; temp.pc = NULL; temp.td = NULL; temp.td_next = xfer->td_start[0]; temp.offset = 0; temp.setup_alt_next = xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr; temp.did_stall = !xfer->flags_int.control_stall; temp.channel = -1; temp.dev_addr = dev_addr; temp.haddr = xfer->xroot->udev->hs_hub_addr; temp.hport = xfer->xroot->udev->hs_port_no; if (xfer->flags_int.usb_mode == USB_MODE_HOST) { speed = usbd_get_speed(xfer->xroot->udev); switch (speed) { case USB_SPEED_LOW: temp.transfer_type = MUSB2_MASK_TI_SPEED_LO; break; case USB_SPEED_FULL: temp.transfer_type = MUSB2_MASK_TI_SPEED_FS; break; case USB_SPEED_HIGH: temp.transfer_type = MUSB2_MASK_TI_SPEED_HS; break; default: temp.transfer_type = 0; DPRINTFN(-1, "Invalid USB speed: %d\n", speed); break; } switch (xfer_type) { case UE_CONTROL: temp.transfer_type |= MUSB2_MASK_TI_PROTO_CTRL; break; case UE_ISOCHRONOUS: temp.transfer_type |= MUSB2_MASK_TI_PROTO_ISOC; break; case UE_BULK: temp.transfer_type |= MUSB2_MASK_TI_PROTO_BULK; break; case UE_INTERRUPT: temp.transfer_type |= MUSB2_MASK_TI_PROTO_INTR; break; default: DPRINTFN(-1, "Invalid USB transfer type: %d\n", xfer_type); break; } temp.transfer_type |= ep_no; td->toggle = xfer->endpoint->toggle_next; } /* check if we should prepend a setup message */ if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) temp.func = &musbotg_dev_ctrl_setup_rx; else temp.func = &musbotg_host_ctrl_setup_tx; temp.len = xfer->frlengths[0]; temp.pc = xfer->frbuffers + 0; temp.short_pkt = temp.len ? 1 : 0; musbotg_setup_standard_chain_sub(&temp); } x = 1; } else { x = 0; } tx = 0; if (x != xfer->nframes) { if (xfer->endpointno & UE_DIR_IN) tx = 1; if (xfer->flags_int.usb_mode == USB_MODE_HOST) { tx = !tx; if (tx) { if (xfer->flags_int.control_xfr) temp.func = &musbotg_host_ctrl_data_tx; else temp.func = &musbotg_host_data_tx; } else { if (xfer->flags_int.control_xfr) temp.func = &musbotg_host_ctrl_data_rx; else temp.func = &musbotg_host_data_rx; } } else { if (tx) { if (xfer->flags_int.control_xfr) temp.func = &musbotg_dev_ctrl_data_tx; else temp.func = &musbotg_dev_data_tx; } else { if (xfer->flags_int.control_xfr) temp.func = &musbotg_dev_ctrl_data_rx; else temp.func = &musbotg_dev_data_rx; } } /* setup "pc" pointer */ temp.pc = xfer->frbuffers + x; } while (x != xfer->nframes) { /* DATA0 / DATA1 message */ temp.len = xfer->frlengths[x]; x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_act) { temp.setup_alt_next = 0; } } else { temp.setup_alt_next = 0; } } if (temp.len == 0) { /* make sure that we send an USB packet */ temp.short_pkt = 0; } else { if (xfer->flags_int.isochronous_xfr) { /* isochronous data transfer */ /* don't force short */ temp.short_pkt = 1; } else { /* regular data transfer */ temp.short_pkt = (xfer->flags.force_short_xfer ? 0 : 1); } } musbotg_setup_standard_chain_sub(&temp); if (xfer->flags_int.isochronous_xfr) { temp.offset += temp.len; } else { /* get next Page Cache pointer */ temp.pc = xfer->frbuffers + x; } } /* check for control transfer */ if (xfer->flags_int.control_xfr) { /* always setup a valid "pc" pointer for status and sync */ temp.pc = xfer->frbuffers + 0; temp.len = 0; temp.short_pkt = 0; temp.setup_alt_next = 0; /* check if we should append a status stage */ if (!xfer->flags_int.control_act) { /* * Send a DATA1 message and invert the current * endpoint direction. */ if (sc->sc_mode == MUSB2_DEVICE_MODE) temp.func = &musbotg_dev_ctrl_status; else { if (xfer->endpointno & UE_DIR_IN) temp.func = musbotg_host_ctrl_status_tx; else temp.func = musbotg_host_ctrl_status_rx; } musbotg_setup_standard_chain_sub(&temp); } } /* must have at least one frame! */ td = temp.td; xfer->td_transfer_last = td; } static void musbotg_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTFN(1, "xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); /* transfer is transferred */ musbotg_device_done(xfer, USB_ERR_TIMEOUT); } static void musbotg_ep_int_set(struct musbotg_softc *sc, int channel, int on) { uint16_t temp; /* * Only enable the endpoint interrupt when we are * actually waiting for data, hence we are dealing * with level triggered interrupts ! */ DPRINTFN(1, "ep_no=%d, on=%d\n", channel, on); if (channel == -1) return; if (channel == 0) { temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE); if (on) temp |= MUSB2_MASK_EPINT(0); else temp &= ~MUSB2_MASK_EPINT(0); MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp); } else { temp = MUSB2_READ_2(sc, MUSB2_REG_INTRXE); if (on) temp |= MUSB2_MASK_EPINT(channel); else temp &= ~MUSB2_MASK_EPINT(channel); MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, temp); temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE); if (on) temp |= MUSB2_MASK_EPINT(channel); else temp &= ~MUSB2_MASK_EPINT(channel); MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp); } if (sc->sc_ep_int_set) sc->sc_ep_int_set(sc, channel, on); } static void musbotg_start_standard_chain(struct usb_xfer *xfer) { DPRINTFN(8, "\n"); /* poll one time */ if (musbotg_xfer_do_fifo(xfer)) { DPRINTFN(14, "enabled interrupts on endpoint\n"); /* put transfer on interrupt queue */ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); /* start timeout, if any */ if (xfer->timeout != 0) { usbd_transfer_timeout_ms(xfer, &musbotg_timeout, xfer->timeout); } } } static void musbotg_root_intr(struct musbotg_softc *sc) { DPRINTFN(8, "\n"); USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* set port bit */ sc->sc_hub_idata[0] = 0x02; /* we only have one port */ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } static usb_error_t musbotg_standard_done_sub(struct usb_xfer *xfer) { struct musbotg_td *td; uint32_t len; uint8_t error; DPRINTFN(8, "\n"); td = xfer->td_transfer_cache; do { len = td->remainder; xfer->endpoint->toggle_next = td->toggle; if (xfer->aframes != xfer->nframes) { /* * Verify the length and subtract * the remainder from "frlengths[]": */ if (len > xfer->frlengths[xfer->aframes]) { td->error = 1; } else { xfer->frlengths[xfer->aframes] -= len; } } /* Check for transfer error */ if (td->error) { /* the transfer is finished */ error = 1; td = NULL; break; } /* Check for short transfer */ if (len > 0) { if (xfer->flags_int.short_frames_ok || xfer->flags_int.isochronous_xfr) { /* follow alt next */ if (td->alt_next) { td = td->obj_next; } else { td = NULL; } } else { /* the transfer is finished */ td = NULL; } error = 0; break; } td = td->obj_next; /* this USB frame is complete */ error = 0; break; } while (0); /* update transfer cache */ xfer->td_transfer_cache = td; return (error ? USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); } static void musbotg_standard_done(struct usb_xfer *xfer) { usb_error_t err = 0; DPRINTFN(12, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { err = musbotg_standard_done_sub(xfer); } xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) { goto done; } } while (xfer->aframes != xfer->nframes) { err = musbotg_standard_done_sub(xfer); xfer->aframes++; if (xfer->td_transfer_cache == NULL) { goto done; } } if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { err = musbotg_standard_done_sub(xfer); } done: musbotg_device_done(xfer, err); } /*------------------------------------------------------------------------* * musbotg_device_done * * NOTE: this function can be called more than one time on the * same USB transfer! *------------------------------------------------------------------------*/ static void musbotg_device_done(struct usb_xfer *xfer, usb_error_t error) { struct musbotg_td *td; struct musbotg_softc *sc; USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); DPRINTFN(1, "xfer=%p, endpoint=%p, error=%d\n", xfer, xfer->endpoint, error); DPRINTFN(14, "disabled interrupts on endpoint\n"); sc = MUSBOTG_BUS2SC(xfer->xroot->bus); td = xfer->td_transfer_cache; if (td && (td->channel != -1)) musbotg_channel_free(sc, td); /* dequeue transfer and start next transfer */ usbd_transfer_done(xfer, error); } static void musbotg_xfer_stall(struct usb_xfer *xfer) { musbotg_device_done(xfer, USB_ERR_STALLED); } static void musbotg_set_stall(struct usb_device *udev, struct usb_endpoint *ep, uint8_t *did_stall) { struct musbotg_softc *sc; uint8_t ep_no; USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); DPRINTFN(4, "endpoint=%p\n", ep); /* set FORCESTALL */ sc = MUSBOTG_BUS2SC(udev->bus); ep_no = (ep->edesc->bEndpointAddress & UE_ADDR); /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no); if (ep->edesc->bEndpointAddress & UE_DIR_IN) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSRL_TXSENDSTALL); } else { MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, MUSB2_MASK_CSRL_RXSENDSTALL); } } static void musbotg_clear_stall_sub(struct musbotg_softc *sc, uint16_t wMaxPacket, uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) { uint16_t mps; uint16_t temp; uint8_t csr; if (ep_type == UE_CONTROL) { /* clearing stall is not needed */ return; } /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no); /* compute max frame size */ mps = wMaxPacket & 0x7FF; switch ((wMaxPacket >> 11) & 3) { case 1: mps *= 2; break; case 2: mps *= 3; break; default: break; } if (ep_dir == UE_DIR_IN) { temp = 0; /* Configure endpoint */ switch (ep_type) { case UE_INTERRUPT: MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, wMaxPacket); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, MUSB2_MASK_CSRH_TXMODE | temp); break; case UE_ISOCHRONOUS: MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, wMaxPacket); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, MUSB2_MASK_CSRH_TXMODE | MUSB2_MASK_CSRH_TXISO | temp); break; case UE_BULK: MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, wMaxPacket); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, MUSB2_MASK_CSRH_TXMODE | temp); break; default: break; } /* Need to flush twice in case of double bufring */ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSRL_TXFFLUSH); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSRL_TXFFLUSH); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); } } /* reset data toggle */ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, MUSB2_MASK_CSRL_TXDT_CLR); MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); /* set double/single buffering */ temp = MUSB2_READ_2(sc, MUSB2_REG_TXDBDIS); if (mps <= (sc->sc_hw_ep_profile[ep_no]. max_in_frame_size / 2)) { /* double buffer */ temp &= ~(1 << ep_no); } else { /* single buffer */ temp |= (1 << ep_no); } MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, temp); /* clear sent stall */ if (csr & MUSB2_MASK_CSRL_TXSENTSTALL) { MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); } } else { temp = 0; /* Configure endpoint */ switch (ep_type) { case UE_INTERRUPT: MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, wMaxPacket); MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, MUSB2_MASK_CSRH_RXNYET | temp); break; case UE_ISOCHRONOUS: MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, wMaxPacket); MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, MUSB2_MASK_CSRH_RXNYET | MUSB2_MASK_CSRH_RXISO | temp); break; case UE_BULK: MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, wMaxPacket); MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, temp); break; default: break; } /* Need to flush twice in case of double bufring */ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); if (csr & MUSB2_MASK_CSRL_RXPKTRDY) { MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, MUSB2_MASK_CSRL_RXFFLUSH); csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); if (csr & MUSB2_MASK_CSRL_RXPKTRDY) { MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, MUSB2_MASK_CSRL_RXFFLUSH); csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); } } /* reset data toggle */ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, MUSB2_MASK_CSRL_RXDT_CLR); MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); /* set double/single buffering */ temp = MUSB2_READ_2(sc, MUSB2_REG_RXDBDIS); if (mps <= (sc->sc_hw_ep_profile[ep_no]. max_out_frame_size / 2)) { /* double buffer */ temp &= ~(1 << ep_no); } else { /* single buffer */ temp |= (1 << ep_no); } MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, temp); /* clear sent stall */ if (csr & MUSB2_MASK_CSRL_RXSENTSTALL) { MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); } } } static void musbotg_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) { struct musbotg_softc *sc; struct usb_endpoint_descriptor *ed; DPRINTFN(4, "endpoint=%p\n", ep); USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); /* check mode */ if (udev->flags.usb_mode != USB_MODE_DEVICE) { /* not supported */ return; } /* get softc */ sc = MUSBOTG_BUS2SC(udev->bus); /* get endpoint descriptor */ ed = ep->edesc; /* reset endpoint */ musbotg_clear_stall_sub(sc, UGETW(ed->wMaxPacketSize), (ed->bEndpointAddress & UE_ADDR), (ed->bmAttributes & UE_XFERTYPE), (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); } usb_error_t musbotg_init(struct musbotg_softc *sc) { struct usb_hw_ep_profile *pf; uint16_t offset; uint8_t nrx; uint8_t ntx; uint8_t temp; uint8_t fsize; uint8_t frx; uint8_t ftx; uint8_t dynfifo; DPRINTFN(1, "start\n"); /* set up the bus structure */ sc->sc_bus.usbrev = USB_REV_2_0; sc->sc_bus.methods = &musbotg_bus_methods; USB_BUS_LOCK(&sc->sc_bus); /* turn on clocks */ if (sc->sc_clocks_on) { (sc->sc_clocks_on) (sc->sc_clocks_arg); } /* wait a little for things to stabilise */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000); /* disable all interrupts */ temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL); DPRINTF("pre-DEVCTL=0x%02x\n", temp); MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0); MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0); MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0); /* disable pullup */ musbotg_pull_common(sc, 0); /* wait a little bit (10ms) */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); /* disable double packet buffering */ MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, 0xFFFF); MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, 0xFFFF); /* enable HighSpeed and ISO Update flags */ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, MUSB2_MASK_HSENAB | MUSB2_MASK_ISOUPD); if (sc->sc_mode == MUSB2_DEVICE_MODE) { /* clear Session bit, if set */ temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL); temp &= ~MUSB2_MASK_SESS; MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp); } else { /* Enter session for Host mode */ temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL); temp |= MUSB2_MASK_SESS; MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp); } /* wait a little for things to stabilise */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 10); DPRINTF("DEVCTL=0x%02x\n", temp); /* disable testmode */ MUSB2_WRITE_1(sc, MUSB2_REG_TESTMODE, 0); /* set default value */ MUSB2_WRITE_1(sc, MUSB2_REG_MISC, 0); /* select endpoint index 0 */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); /* read out number of endpoints */ nrx = (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) / 16); ntx = (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) % 16); /* these numbers exclude the control endpoint */ DPRINTFN(2, "RX/TX endpoints: %u/%u\n", nrx, ntx); sc->sc_ep_max = (nrx > ntx) ? nrx : ntx; if (sc->sc_ep_max == 0) { DPRINTFN(2, "ERROR: Looks like the clocks are off!\n"); } /* read out configuration data */ sc->sc_conf_data = MUSB2_READ_1(sc, MUSB2_REG_CONFDATA); DPRINTFN(2, "Config Data: 0x%02x\n", sc->sc_conf_data); dynfifo = (sc->sc_conf_data & MUSB2_MASK_CD_DYNFIFOSZ) ? 1 : 0; if (dynfifo) { device_printf(sc->sc_bus.bdev, "Dynamic FIFO sizing detected, " "assuming 16Kbytes of FIFO RAM\n"); } DPRINTFN(2, "HW version: 0x%04x\n", MUSB2_READ_1(sc, MUSB2_REG_HWVERS)); /* initialise endpoint profiles */ offset = 0; for (temp = 1; temp <= sc->sc_ep_max; temp++) { pf = sc->sc_hw_ep_profile + temp; /* select endpoint */ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, temp); fsize = MUSB2_READ_1(sc, MUSB2_REG_FSIZE); frx = (fsize & MUSB2_MASK_RX_FSIZE) / 16; ftx = (fsize & MUSB2_MASK_TX_FSIZE); DPRINTF("Endpoint %u FIFO size: IN=%u, OUT=%u, DYN=%d\n", temp, ftx, frx, dynfifo); if (dynfifo) { if (frx && (temp <= nrx)) { if (temp == 1) { frx = 12; /* 4K */ MUSB2_WRITE_1(sc, MUSB2_REG_RXFIFOSZ, MUSB2_VAL_FIFOSZ_4096 | MUSB2_MASK_FIFODB); } else if (temp < 8) { frx = 10; /* 1K */ MUSB2_WRITE_1(sc, MUSB2_REG_RXFIFOSZ, MUSB2_VAL_FIFOSZ_512 | MUSB2_MASK_FIFODB); } else { frx = 7; /* 128 bytes */ MUSB2_WRITE_1(sc, MUSB2_REG_RXFIFOSZ, MUSB2_VAL_FIFOSZ_128); } MUSB2_WRITE_2(sc, MUSB2_REG_RXFIFOADD, offset >> 3); offset += (1 << frx); } if (ftx && (temp <= ntx)) { if (temp == 1) { ftx = 12; /* 4K */ MUSB2_WRITE_1(sc, MUSB2_REG_TXFIFOSZ, MUSB2_VAL_FIFOSZ_4096 | MUSB2_MASK_FIFODB); } else if (temp < 8) { ftx = 10; /* 1K */ MUSB2_WRITE_1(sc, MUSB2_REG_TXFIFOSZ, MUSB2_VAL_FIFOSZ_512 | MUSB2_MASK_FIFODB); } else { ftx = 7; /* 128 bytes */ MUSB2_WRITE_1(sc, MUSB2_REG_TXFIFOSZ, MUSB2_VAL_FIFOSZ_128); } MUSB2_WRITE_2(sc, MUSB2_REG_TXFIFOADD, offset >> 3); offset += (1 << ftx); } } if (frx && ftx && (temp <= nrx) && (temp <= ntx)) { pf->max_in_frame_size = 1 << ftx; pf->max_out_frame_size = 1 << frx; pf->is_simplex = 0; /* duplex */ pf->support_multi_buffer = 1; pf->support_bulk = 1; pf->support_interrupt = 1; pf->support_isochronous = 1; pf->support_in = 1; pf->support_out = 1; } else if (frx && (temp <= nrx)) { pf->max_out_frame_size = 1 << frx; pf->max_in_frame_size = 0; pf->is_simplex = 1; /* simplex */ pf->support_multi_buffer = 1; pf->support_bulk = 1; pf->support_interrupt = 1; pf->support_isochronous = 1; pf->support_out = 1; } else if (ftx && (temp <= ntx)) { pf->max_in_frame_size = 1 << ftx; pf->max_out_frame_size = 0; pf->is_simplex = 1; /* simplex */ pf->support_multi_buffer = 1; pf->support_bulk = 1; pf->support_interrupt = 1; pf->support_isochronous = 1; pf->support_in = 1; } } DPRINTFN(2, "Dynamic FIFO size = %d bytes\n", offset); /* turn on default interrupts */ if (sc->sc_mode == MUSB2_HOST_MODE) MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0xff); else MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, MUSB2_MASK_IRESET); musbotg_clocks_off(sc); USB_BUS_UNLOCK(&sc->sc_bus); /* catch any lost interrupts */ musbotg_do_poll(&sc->sc_bus); return (0); /* success */ } void musbotg_uninit(struct musbotg_softc *sc) { USB_BUS_LOCK(&sc->sc_bus); /* disable all interrupts */ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0); MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0); MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0); sc->sc_flags.port_powered = 0; sc->sc_flags.status_vbus = 0; sc->sc_flags.status_bus_reset = 0; sc->sc_flags.status_suspend = 0; sc->sc_flags.change_suspend = 0; sc->sc_flags.change_connect = 1; musbotg_pull_down(sc); musbotg_clocks_off(sc); USB_BUS_UNLOCK(&sc->sc_bus); } static void musbotg_do_poll(struct usb_bus *bus) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); musbotg_interrupt_poll(sc); USB_BUS_UNLOCK(&sc->sc_bus); } /*------------------------------------------------------------------------* * musbotg bulk support *------------------------------------------------------------------------*/ static void musbotg_device_bulk_open(struct usb_xfer *xfer) { return; } static void musbotg_device_bulk_close(struct usb_xfer *xfer) { musbotg_device_done(xfer, USB_ERR_CANCELLED); } static void musbotg_device_bulk_enter(struct usb_xfer *xfer) { return; } static void musbotg_device_bulk_start(struct usb_xfer *xfer) { /* setup TDs */ musbotg_setup_standard_chain(xfer); musbotg_start_standard_chain(xfer); } static const struct usb_pipe_methods musbotg_device_bulk_methods = { .open = musbotg_device_bulk_open, .close = musbotg_device_bulk_close, .enter = musbotg_device_bulk_enter, .start = musbotg_device_bulk_start, }; /*------------------------------------------------------------------------* * musbotg control support *------------------------------------------------------------------------*/ static void musbotg_device_ctrl_open(struct usb_xfer *xfer) { return; } static void musbotg_device_ctrl_close(struct usb_xfer *xfer) { musbotg_device_done(xfer, USB_ERR_CANCELLED); } static void musbotg_device_ctrl_enter(struct usb_xfer *xfer) { return; } static void musbotg_device_ctrl_start(struct usb_xfer *xfer) { /* setup TDs */ musbotg_setup_standard_chain(xfer); musbotg_start_standard_chain(xfer); } static const struct usb_pipe_methods musbotg_device_ctrl_methods = { .open = musbotg_device_ctrl_open, .close = musbotg_device_ctrl_close, .enter = musbotg_device_ctrl_enter, .start = musbotg_device_ctrl_start, }; /*------------------------------------------------------------------------* * musbotg interrupt support *------------------------------------------------------------------------*/ static void musbotg_device_intr_open(struct usb_xfer *xfer) { return; } static void musbotg_device_intr_close(struct usb_xfer *xfer) { musbotg_device_done(xfer, USB_ERR_CANCELLED); } static void musbotg_device_intr_enter(struct usb_xfer *xfer) { return; } static void musbotg_device_intr_start(struct usb_xfer *xfer) { /* setup TDs */ musbotg_setup_standard_chain(xfer); musbotg_start_standard_chain(xfer); } static const struct usb_pipe_methods musbotg_device_intr_methods = { .open = musbotg_device_intr_open, .close = musbotg_device_intr_close, .enter = musbotg_device_intr_enter, .start = musbotg_device_intr_start, }; /*------------------------------------------------------------------------* * musbotg full speed isochronous support *------------------------------------------------------------------------*/ static void musbotg_device_isoc_open(struct usb_xfer *xfer) { return; } static void musbotg_device_isoc_close(struct usb_xfer *xfer) { musbotg_device_done(xfer, USB_ERR_CANCELLED); } static void musbotg_device_isoc_enter(struct usb_xfer *xfer) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus); uint32_t temp; uint32_t nframes; uint32_t fs_frames; DPRINTFN(5, "xfer=%p next=%d nframes=%d\n", xfer, xfer->endpoint->isoc_next, xfer->nframes); /* get the current frame index */ nframes = MUSB2_READ_2(sc, MUSB2_REG_FRAME); /* * check if the frame index is within the window where the frames * will be inserted */ temp = (nframes - xfer->endpoint->isoc_next) & MUSB2_MASK_FRAME; if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) { fs_frames = (xfer->nframes + 7) / 8; } else { fs_frames = xfer->nframes; } if ((xfer->endpoint->is_synced == 0) || (temp < fs_frames)) { /* * If there is data underflow or the pipe queue is * empty we schedule the transfer a few frames ahead * of the current frame position. Else two isochronous * transfers might overlap. */ xfer->endpoint->isoc_next = (nframes + 3) & MUSB2_MASK_FRAME; xfer->endpoint->is_synced = 1; DPRINTFN(2, "start next=%d\n", xfer->endpoint->isoc_next); } /* * compute how many milliseconds the insertion is ahead of the * current frame position: */ temp = (xfer->endpoint->isoc_next - nframes) & MUSB2_MASK_FRAME; /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + fs_frames; /* compute frame number for next insertion */ xfer->endpoint->isoc_next += fs_frames; /* setup TDs */ musbotg_setup_standard_chain(xfer); } static void musbotg_device_isoc_start(struct usb_xfer *xfer) { /* start TD chain */ musbotg_start_standard_chain(xfer); } static const struct usb_pipe_methods musbotg_device_isoc_methods = { .open = musbotg_device_isoc_open, .close = musbotg_device_isoc_close, .enter = musbotg_device_isoc_enter, .start = musbotg_device_isoc_start, }; /*------------------------------------------------------------------------* * musbotg root control support *------------------------------------------------------------------------* * Simulate a hardware HUB by handling all the necessary requests. *------------------------------------------------------------------------*/ static const struct usb_device_descriptor musbotg_devd = { .bLength = sizeof(struct usb_device_descriptor), .bDescriptorType = UDESC_DEVICE, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_HSHUBSTT, .bMaxPacketSize = 64, .bcdDevice = {0x00, 0x01}, .iManufacturer = 1, .iProduct = 2, .bNumConfigurations = 1, }; static const struct usb_device_qualifier musbotg_odevd = { .bLength = sizeof(struct usb_device_qualifier), .bDescriptorType = UDESC_DEVICE_QUALIFIER, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_FSHUB, .bMaxPacketSize0 = 0, .bNumConfigurations = 0, }; static const struct musbotg_config_desc musbotg_confd = { .confd = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(musbotg_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0, }, .ifcd = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = 0, }, .endpd = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = (UE_DIR_IN | MUSBOTG_INTR_ENDPT), .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 8, .bInterval = 255, }, }; #define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) } static const struct usb_hub_descriptor_min musbotg_hubd = { .bDescLength = sizeof(musbotg_hubd), .bDescriptorType = UDESC_HUB, .bNbrPorts = 1, HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)), .bPwrOn2PwrGood = 50, .bHubContrCurrent = 0, .DeviceRemovable = {0}, /* port is removable */ }; #define STRING_VENDOR \ "M\0e\0n\0t\0o\0r\0 \0G\0r\0a\0p\0h\0i\0c\0s" #define STRING_PRODUCT \ "O\0T\0G\0 \0R\0o\0o\0t\0 \0H\0U\0B" USB_MAKE_STRING_DESC(STRING_VENDOR, musbotg_vendor); USB_MAKE_STRING_DESC(STRING_PRODUCT, musbotg_product); static usb_error_t musbotg_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus); const void *ptr; uint16_t len; uint16_t value; uint16_t index; uint8_t reg; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* buffer reset */ ptr = (const void *)&sc->sc_hub_temp; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); /* demultiplex the control request */ switch (req->bmRequestType) { case UT_READ_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_descriptor; case UR_GET_CONFIG: goto tr_handle_get_config; case UR_GET_STATUS: goto tr_handle_get_status; default: goto tr_stalled; } break; case UT_WRITE_DEVICE: switch (req->bRequest) { case UR_SET_ADDRESS: goto tr_handle_set_address; case UR_SET_CONFIG: goto tr_handle_set_config; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_DESCRIPTOR: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_WRITE_ENDPOINT: switch (req->bRequest) { case UR_CLEAR_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_clear_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_clear_wakeup; default: goto tr_stalled; } break; case UR_SET_FEATURE: switch (UGETW(req->wValue)) { case UF_ENDPOINT_HALT: goto tr_handle_set_halt; case UF_DEVICE_REMOTE_WAKEUP: goto tr_handle_set_wakeup; default: goto tr_stalled; } break; case UR_SYNCH_FRAME: goto tr_valid; /* nop */ default: goto tr_stalled; } break; case UT_READ_ENDPOINT: switch (req->bRequest) { case UR_GET_STATUS: goto tr_handle_get_ep_status; default: goto tr_stalled; } break; case UT_WRITE_INTERFACE: switch (req->bRequest) { case UR_SET_INTERFACE: goto tr_handle_set_interface; case UR_CLEAR_FEATURE: goto tr_valid; /* nop */ case UR_SET_FEATURE: default: goto tr_stalled; } break; case UT_READ_INTERFACE: switch (req->bRequest) { case UR_GET_INTERFACE: goto tr_handle_get_interface; case UR_GET_STATUS: goto tr_handle_get_iface_status; default: goto tr_stalled; } break; case UT_WRITE_CLASS_INTERFACE: case UT_WRITE_VENDOR_INTERFACE: /* XXX forward */ break; case UT_READ_CLASS_INTERFACE: case UT_READ_VENDOR_INTERFACE: /* XXX forward */ break; case UT_WRITE_CLASS_DEVICE: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_valid; case UR_SET_DESCRIPTOR: case UR_SET_FEATURE: break; default: goto tr_stalled; } break; case UT_WRITE_CLASS_OTHER: switch (req->bRequest) { case UR_CLEAR_FEATURE: goto tr_handle_clear_port_feature; case UR_SET_FEATURE: goto tr_handle_set_port_feature; case UR_CLEAR_TT_BUFFER: case UR_RESET_TT: case UR_STOP_TT: goto tr_valid; default: goto tr_stalled; } break; case UT_READ_CLASS_OTHER: switch (req->bRequest) { case UR_GET_TT_STATE: goto tr_handle_get_tt_state; case UR_GET_STATUS: goto tr_handle_get_port_status; default: goto tr_stalled; } break; case UT_READ_CLASS_DEVICE: switch (req->bRequest) { case UR_GET_DESCRIPTOR: goto tr_handle_get_class_descriptor; case UR_GET_STATUS: goto tr_handle_get_class_status; default: goto tr_stalled; } break; default: goto tr_stalled; } goto tr_valid; tr_handle_get_descriptor: switch (value >> 8) { case UDESC_DEVICE: if (value & 0xff) { goto tr_stalled; } len = sizeof(musbotg_devd); ptr = (const void *)&musbotg_devd; goto tr_valid; case UDESC_DEVICE_QUALIFIER: if (value & 0xff) { goto tr_stalled; } len = sizeof(musbotg_odevd); ptr = (const void *)&musbotg_odevd; goto tr_valid; case UDESC_CONFIG: if (value & 0xff) { goto tr_stalled; } len = sizeof(musbotg_confd); ptr = (const void *)&musbotg_confd; goto tr_valid; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ len = sizeof(usb_string_lang_en); ptr = (const void *)&usb_string_lang_en; goto tr_valid; case 1: /* Vendor */ len = sizeof(musbotg_vendor); ptr = (const void *)&musbotg_vendor; goto tr_valid; case 2: /* Product */ len = sizeof(musbotg_product); ptr = (const void *)&musbotg_product; goto tr_valid; default: break; } break; default: goto tr_stalled; } goto tr_stalled; tr_handle_get_config: len = 1; sc->sc_hub_temp.wValue[0] = sc->sc_conf; goto tr_valid; tr_handle_get_status: len = 2; USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); goto tr_valid; tr_handle_set_address: if (value & 0xFF00) { goto tr_stalled; } sc->sc_rt_addr = value; goto tr_valid; tr_handle_set_config: if (value >= 2) { goto tr_stalled; } sc->sc_conf = value; goto tr_valid; tr_handle_get_interface: len = 1; sc->sc_hub_temp.wValue[0] = 0; goto tr_valid; tr_handle_get_tt_state: tr_handle_get_class_status: tr_handle_get_iface_status: tr_handle_get_ep_status: len = 2; USETW(sc->sc_hub_temp.wValue, 0); goto tr_valid; tr_handle_set_halt: tr_handle_set_interface: tr_handle_set_wakeup: tr_handle_clear_wakeup: tr_handle_clear_halt: goto tr_valid; tr_handle_clear_port_feature: if (index != 1) { goto tr_stalled; } DPRINTFN(8, "UR_CLEAR_PORT_FEATURE on port %d\n", index); switch (value) { case UHF_PORT_SUSPEND: if (sc->sc_mode == MUSB2_HOST_MODE) musbotg_wakeup_host(sc); else musbotg_wakeup_peer(sc); break; case UHF_PORT_ENABLE: sc->sc_flags.port_enabled = 0; break; case UHF_C_PORT_ENABLE: sc->sc_flags.change_enabled = 0; break; case UHF_C_PORT_OVER_CURRENT: sc->sc_flags.change_over_current = 0; break; case UHF_C_PORT_RESET: sc->sc_flags.change_reset = 0; break; case UHF_PORT_TEST: case UHF_PORT_INDICATOR: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 0; musbotg_pull_down(sc); musbotg_clocks_off(sc); break; case UHF_C_PORT_CONNECTION: sc->sc_flags.change_connect = 0; break; case UHF_C_PORT_SUSPEND: sc->sc_flags.change_suspend = 0; break; default: err = USB_ERR_IOERROR; goto done; } goto tr_valid; tr_handle_set_port_feature: if (index != 1) { goto tr_stalled; } DPRINTFN(8, "UR_SET_PORT_FEATURE\n"); switch (value) { case UHF_PORT_ENABLE: sc->sc_flags.port_enabled = 1; break; case UHF_PORT_SUSPEND: if (sc->sc_mode == MUSB2_HOST_MODE) musbotg_suspend_host(sc); break; case UHF_PORT_RESET: if (sc->sc_mode == MUSB2_HOST_MODE) { reg = MUSB2_READ_1(sc, MUSB2_REG_POWER); reg |= MUSB2_MASK_RESET; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, reg); /* Wait for 20 msec */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 5); reg = MUSB2_READ_1(sc, MUSB2_REG_POWER); reg &= ~MUSB2_MASK_RESET; MUSB2_WRITE_1(sc, MUSB2_REG_POWER, reg); /* determine line speed */ reg = MUSB2_READ_1(sc, MUSB2_REG_POWER); if (reg & MUSB2_MASK_HSMODE) sc->sc_flags.status_high_speed = 1; else sc->sc_flags.status_high_speed = 0; sc->sc_flags.change_reset = 1; } else err = USB_ERR_IOERROR; break; case UHF_PORT_TEST: case UHF_PORT_INDICATOR: /* nops */ break; case UHF_PORT_POWER: sc->sc_flags.port_powered = 1; break; default: err = USB_ERR_IOERROR; goto done; } goto tr_valid; tr_handle_get_port_status: DPRINTFN(8, "UR_GET_PORT_STATUS\n"); if (index != 1) { goto tr_stalled; } if (sc->sc_flags.status_vbus) { musbotg_clocks_on(sc); musbotg_pull_up(sc); } else { musbotg_pull_down(sc); musbotg_clocks_off(sc); } /* Select Device Side Mode */ if (sc->sc_mode == MUSB2_DEVICE_MODE) value = UPS_PORT_MODE_DEVICE; else value = 0; if (sc->sc_flags.status_high_speed) { value |= UPS_HIGH_SPEED; } if (sc->sc_flags.port_powered) { value |= UPS_PORT_POWER; } if (sc->sc_flags.port_enabled) { value |= UPS_PORT_ENABLED; } if (sc->sc_flags.port_over_current) value |= UPS_OVERCURRENT_INDICATOR; if (sc->sc_flags.status_vbus && sc->sc_flags.status_bus_reset) { value |= UPS_CURRENT_CONNECT_STATUS; } if (sc->sc_flags.status_suspend) { value |= UPS_SUSPEND; } USETW(sc->sc_hub_temp.ps.wPortStatus, value); value = 0; if (sc->sc_flags.change_connect) { value |= UPS_C_CONNECT_STATUS; if (sc->sc_mode == MUSB2_DEVICE_MODE) { if (sc->sc_flags.status_vbus && sc->sc_flags.status_bus_reset) { /* reset EP0 state */ sc->sc_ep0_busy = 0; sc->sc_ep0_cmd = 0; } } } if (sc->sc_flags.change_suspend) value |= UPS_C_SUSPEND; if (sc->sc_flags.change_reset) value |= UPS_C_PORT_RESET; if (sc->sc_flags.change_over_current) value |= UPS_C_OVERCURRENT_INDICATOR; USETW(sc->sc_hub_temp.ps.wPortChange, value); len = sizeof(sc->sc_hub_temp.ps); goto tr_valid; tr_handle_get_class_descriptor: if (value & 0xFF) { goto tr_stalled; } ptr = (const void *)&musbotg_hubd; len = sizeof(musbotg_hubd); goto tr_valid; tr_stalled: err = USB_ERR_STALLED; tr_valid: done: *plength = len; *pptr = ptr; return (err); } static void musbotg_xfer_setup(struct usb_setup_params *parm) { struct musbotg_softc *sc; struct usb_xfer *xfer; void *last_obj; uint32_t ntd; uint32_t n; uint8_t ep_no; sc = MUSBOTG_BUS2SC(parm->udev->bus); xfer = parm->curr_xfer; /* * NOTE: This driver does not use any of the parameters that * are computed from the following values. Just set some * reasonable dummies: */ parm->hc_max_packet_size = 0x400; parm->hc_max_frame_size = 0xc00; if ((parm->methods == &musbotg_device_isoc_methods) || (parm->methods == &musbotg_device_intr_methods)) parm->hc_max_packet_count = 3; else parm->hc_max_packet_count = 1; usbd_transfer_setup_sub(parm); /* * compute maximum number of TDs */ if (parm->methods == &musbotg_device_ctrl_methods) { ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ; } else if (parm->methods == &musbotg_device_bulk_methods) { ntd = xfer->nframes + 1 /* SYNC */ ; } else if (parm->methods == &musbotg_device_intr_methods) { ntd = xfer->nframes + 1 /* SYNC */ ; } else if (parm->methods == &musbotg_device_isoc_methods) { ntd = xfer->nframes + 1 /* SYNC */ ; } else { ntd = 0; } /* * check if "usbd_transfer_setup_sub" set an error */ if (parm->err) { return; } /* * allocate transfer descriptors */ last_obj = NULL; ep_no = xfer->endpointno & UE_ADDR; /* * Check for a valid endpoint profile in USB device mode: */ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { const struct usb_hw_ep_profile *pf; musbotg_get_hw_ep_profile(parm->udev, &pf, ep_no); if (pf == NULL) { /* should not happen */ parm->err = USB_ERR_INVAL; return; } } /* align data */ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); for (n = 0; n != ntd; n++) { struct musbotg_td *td; if (parm->buf) { td = USB_ADD_BYTES(parm->buf, parm->size[0]); /* init TD */ td->max_frame_size = xfer->max_frame_size; td->reg_max_packet = xfer->max_packet_size | ((xfer->max_packet_count - 1) << 11); td->ep_no = ep_no; td->obj_next = last_obj; last_obj = td; } parm->size[0] += sizeof(*td); } xfer->td_start[0] = last_obj; } static void musbotg_xfer_unsetup(struct usb_xfer *xfer) { return; } static void musbotg_get_dma_delay(struct usb_device *udev, uint32_t *pus) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus); if (sc->sc_mode == MUSB2_HOST_MODE) *pus = 2000; /* microseconds */ else *pus = 0; } static void musbotg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus); DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode, sc->sc_rt_addr); if (udev->device_index != sc->sc_rt_addr) { switch (edesc->bmAttributes & UE_XFERTYPE) { case UE_CONTROL: ep->methods = &musbotg_device_ctrl_methods; break; case UE_INTERRUPT: ep->methods = &musbotg_device_intr_methods; break; case UE_ISOCHRONOUS: ep->methods = &musbotg_device_isoc_methods; break; case UE_BULK: ep->methods = &musbotg_device_bulk_methods; break; default: /* do nothing */ break; } } } static void musbotg_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus); switch (state) { case USB_HW_POWER_SUSPEND: musbotg_uninit(sc); break; case USB_HW_POWER_SHUTDOWN: musbotg_uninit(sc); break; case USB_HW_POWER_RESUME: musbotg_init(sc); break; default: break; } } static const struct usb_bus_methods musbotg_bus_methods = { .endpoint_init = &musbotg_ep_init, .get_dma_delay = &musbotg_get_dma_delay, .xfer_setup = &musbotg_xfer_setup, .xfer_unsetup = &musbotg_xfer_unsetup, .get_hw_ep_profile = &musbotg_get_hw_ep_profile, .xfer_stall = &musbotg_xfer_stall, .set_stall = &musbotg_set_stall, .clear_stall = &musbotg_clear_stall, .roothub_exec = &musbotg_roothub_exec, .xfer_poll = &musbotg_do_poll, .set_hw_power_sleep = &musbotg_set_hw_power_sleep, }; Index: projects/release-pkg/sys/dev/usb/serial/uftdi.c =================================================================== --- projects/release-pkg/sys/dev/usb/serial/uftdi.c (revision 297604) +++ projects/release-pkg/sys/dev/usb/serial/uftdi.c (revision 297605) @@ -1,1976 +1,2003 @@ /* $NetBSD: uftdi.c,v 1.13 2002/09/23 05:51:23 simonb Exp $ */ /*- * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net). * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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$"); /* * NOTE: all function names beginning like "uftdi_cfg_" can only * be called from within the config thread function ! */ /* * FTDI FT232x, FT2232x, FT4232x, FT8U100AX and FT8U232xM serial adapters. * * Note that we specifically do not do a reset or otherwise alter the state of * the chip during attach, detach, open, and close, because it could be * pre-initialized (via an attached serial eeprom) to power-on into a mode such * as bitbang in which the pins are being driven to a specific state which we * must not perturb. The device gets reset at power-on, and doesn't need to be * reset again after that to function, except as directed by ioctl() calls. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR uftdi_debug #include #include #include #include #include static SYSCTL_NODE(_hw_usb, OID_AUTO, uftdi, CTLFLAG_RW, 0, "USB uftdi"); #ifdef USB_DEBUG static int uftdi_debug = 0; SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, debug, CTLFLAG_RWTUN, &uftdi_debug, 0, "Debug level"); #endif #define UFTDI_CONFIG_INDEX 0 /* * IO buffer sizes and FTDI device procotol sizes. * * Note that the output packet size in the following defines is not the usb * protocol packet size based on bus speed, it is the size dictated by the FTDI * device itself, and is used only on older chips. * * We allocate buffers bigger than the hardware's packet size, and process * multiple packets within each buffer. This allows the controller to make * optimal use of the usb bus by conducting multiple transfers with the device * during a single bus timeslice to fill or drain the chip's fifos. * * The output data on newer chips has no packet header, and we are able to pack * any number of output bytes into a buffer. On some older chips, each output * packet contains a 1-byte header and up to 63 bytes of payload. The size is * encoded in 6 bits of the header, hence the 64-byte limit on packet size. We * loop to fill the buffer with many of these header+payload packets. * * The input data on all chips consists of packets which contain a 2-byte header * followed by data payload. The total size of the packet is wMaxPacketSize * which can change based on the bus speed (e.g., 64 for full speed, 512 for * high speed). We loop to extract the headers and payloads from the packets * packed into an input buffer. */ #define UFTDI_IBUFSIZE 2048 #define UFTDI_IHDRSIZE 2 #define UFTDI_OBUFSIZE 2048 #define UFTDI_OPKTSIZE 64 enum { UFTDI_BULK_DT_WR, UFTDI_BULK_DT_RD, UFTDI_N_TRANSFER, }; enum { DEVT_SIO, DEVT_232A, DEVT_232B, DEVT_2232D, /* Includes 2232C */ DEVT_232R, DEVT_2232H, DEVT_4232H, DEVT_232H, DEVT_230X, }; #define DEVF_BAUDBITS_HINDEX 0x01 /* Baud bits in high byte of index. */ #define DEVF_BAUDCLK_12M 0X02 /* Base baud clock is 12MHz. */ struct uftdi_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct usb_device *sc_udev; struct usb_xfer *sc_xfer[UFTDI_N_TRANSFER]; device_t sc_dev; struct mtx sc_mtx; uint32_t sc_unit; uint16_t sc_last_lcr; uint16_t sc_bcdDevice; uint8_t sc_devtype; uint8_t sc_devflags; uint8_t sc_hdrlen; uint8_t sc_msr; uint8_t sc_lsr; uint8_t sc_bitmode; }; struct uftdi_param_config { uint16_t baud_lobits; uint16_t baud_hibits; uint16_t lcr; uint8_t v_start; uint8_t v_stop; uint8_t v_flow; }; /* prototypes */ static device_probe_t uftdi_probe; static device_attach_t uftdi_attach; static device_detach_t uftdi_detach; static void uftdi_free_softc(struct uftdi_softc *); static usb_callback_t uftdi_write_callback; static usb_callback_t uftdi_read_callback; static void uftdi_free(struct ucom_softc *); static void uftdi_cfg_open(struct ucom_softc *); static void uftdi_cfg_close(struct ucom_softc *); static void uftdi_cfg_set_dtr(struct ucom_softc *, uint8_t); static void uftdi_cfg_set_rts(struct ucom_softc *, uint8_t); static void uftdi_cfg_set_break(struct ucom_softc *, uint8_t); static int uftdi_set_parm_soft(struct ucom_softc *, struct termios *, struct uftdi_param_config *); static int uftdi_pre_param(struct ucom_softc *, struct termios *); static void uftdi_cfg_param(struct ucom_softc *, struct termios *); static void uftdi_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static int uftdi_reset(struct ucom_softc *, int); static int uftdi_set_bitmode(struct ucom_softc *, uint8_t, uint8_t); static int uftdi_get_bitmode(struct ucom_softc *, uint8_t *, uint8_t *); static int uftdi_set_latency(struct ucom_softc *, int); static int uftdi_get_latency(struct ucom_softc *, int *); static int uftdi_set_event_char(struct ucom_softc *, int); static int uftdi_set_error_char(struct ucom_softc *, int); static int uftdi_ioctl(struct ucom_softc *, uint32_t, caddr_t, int, struct thread *); static void uftdi_start_read(struct ucom_softc *); static void uftdi_stop_read(struct ucom_softc *); static void uftdi_start_write(struct ucom_softc *); static void uftdi_stop_write(struct ucom_softc *); static void uftdi_poll(struct ucom_softc *ucom); static const struct usb_config uftdi_config[UFTDI_N_TRANSFER] = { [UFTDI_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UFTDI_OBUFSIZE, .flags = {.pipe_bof = 1,}, .callback = &uftdi_write_callback, }, [UFTDI_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UFTDI_IBUFSIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &uftdi_read_callback, }, }; static const struct ucom_callback uftdi_callback = { .ucom_cfg_get_status = &uftdi_cfg_get_status, .ucom_cfg_set_dtr = &uftdi_cfg_set_dtr, .ucom_cfg_set_rts = &uftdi_cfg_set_rts, .ucom_cfg_set_break = &uftdi_cfg_set_break, .ucom_cfg_param = &uftdi_cfg_param, .ucom_cfg_open = &uftdi_cfg_open, .ucom_cfg_close = &uftdi_cfg_close, .ucom_pre_param = &uftdi_pre_param, .ucom_ioctl = &uftdi_ioctl, .ucom_start_read = &uftdi_start_read, .ucom_stop_read = &uftdi_stop_read, .ucom_start_write = &uftdi_start_write, .ucom_stop_write = &uftdi_stop_write, .ucom_poll = &uftdi_poll, .ucom_free = &uftdi_free, }; static device_method_t uftdi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, uftdi_probe), DEVMETHOD(device_attach, uftdi_attach), DEVMETHOD(device_detach, uftdi_detach), DEVMETHOD_END }; static devclass_t uftdi_devclass; static driver_t uftdi_driver = { .name = "uftdi", .methods = uftdi_methods, .size = sizeof(struct uftdi_softc), }; static const STRUCT_USB_HOST_ID uftdi_devs[] = { #define UFTDI_DEV(v, p, i) \ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } UFTDI_DEV(ACTON, SPECTRAPRO, 0), UFTDI_DEV(ALTI2, N3, 0), UFTDI_DEV(ANALOGDEVICES, GNICE, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(ANALOGDEVICES, GNICEPLUS, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(ATMEL, STK541, 0), UFTDI_DEV(BAYER, CONTOUR_CABLE, 0), UFTDI_DEV(BBELECTRONICS, 232USB9M, 0), UFTDI_DEV(BBELECTRONICS, 485USB9F_2W, 0), UFTDI_DEV(BBELECTRONICS, 485USB9F_4W, 0), UFTDI_DEV(BBELECTRONICS, 485USBTB_2W, 0), UFTDI_DEV(BBELECTRONICS, 485USBTB_4W, 0), UFTDI_DEV(BBELECTRONICS, TTL3USB9M, 0), UFTDI_DEV(BBELECTRONICS, TTL5USB9M, 0), UFTDI_DEV(BBELECTRONICS, USO9ML2, 0), UFTDI_DEV(BBELECTRONICS, USO9ML2DR, 0), UFTDI_DEV(BBELECTRONICS, USO9ML2DR_2, 0), UFTDI_DEV(BBELECTRONICS, USOPTL4, 0), UFTDI_DEV(BBELECTRONICS, USOPTL4DR, 0), UFTDI_DEV(BBELECTRONICS, USOPTL4DR2, 0), UFTDI_DEV(BBELECTRONICS, USOTL4, 0), UFTDI_DEV(BBELECTRONICS, USPTL4, 0), UFTDI_DEV(BBELECTRONICS, USTL4, 0), UFTDI_DEV(BBELECTRONICS, ZZ_PROG1_USB, 0), UFTDI_DEV(CONTEC, COM1USBH, 0), UFTDI_DEV(DRESDENELEKTRONIK, SENSORTERMINALBOARD, 0), UFTDI_DEV(DRESDENELEKTRONIK, WIRELESSHANDHELDTERMINAL, 0), UFTDI_DEV(DRESDENELEKTRONIK, DE_RFNODE, 0), UFTDI_DEV(DRESDENELEKTRONIK, LEVELSHIFTERSTICKLOWCOST, 0), UFTDI_DEV(ELEKTOR, FT323R, 0), UFTDI_DEV(EVOLUTION, ER1, 0), UFTDI_DEV(EVOLUTION, HYBRID, 0), UFTDI_DEV(EVOLUTION, RCM4, 0), UFTDI_DEV(FALCOM, SAMBA, 0), UFTDI_DEV(FALCOM, TWIST, 0), UFTDI_DEV(FIC, NEO1973_DEBUG, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FIC, NEO1973_DEBUG, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, 232EX, 0), UFTDI_DEV(FTDI, 232H, 0), UFTDI_DEV(FTDI, 232RL, 0), UFTDI_DEV(FTDI, 4N_GALAXY_DE_1, 0), UFTDI_DEV(FTDI, 4N_GALAXY_DE_2, 0), UFTDI_DEV(FTDI, 4N_GALAXY_DE_3, 0), UFTDI_DEV(FTDI, 8U232AM_ALT, 0), UFTDI_DEV(FTDI, ACCESSO, 0), UFTDI_DEV(FTDI, ACG_HFDUAL, 0), UFTDI_DEV(FTDI, ACTIVE_ROBOTS, 0), UFTDI_DEV(FTDI, ACTZWAVE, 0), UFTDI_DEV(FTDI, AMC232, 0), UFTDI_DEV(FTDI, ARTEMIS, 0), UFTDI_DEV(FTDI, ASK_RDR400, 0), UFTDI_DEV(FTDI, ATIK_ATK16, 0), UFTDI_DEV(FTDI, ATIK_ATK16C, 0), UFTDI_DEV(FTDI, ATIK_ATK16HR, 0), UFTDI_DEV(FTDI, ATIK_ATK16HRC, 0), UFTDI_DEV(FTDI, ATIK_ATK16IC, 0), UFTDI_DEV(FTDI, BCS_SE923, 0), UFTDI_DEV(FTDI, CANDAPTER, 0), UFTDI_DEV(FTDI, CANUSB, 0), UFTDI_DEV(FTDI, CCSICDU20_0, 0), UFTDI_DEV(FTDI, CCSICDU40_1, 0), UFTDI_DEV(FTDI, CCSICDU64_4, 0), UFTDI_DEV(FTDI, CCSLOAD_N_GO_3, 0), UFTDI_DEV(FTDI, CCSMACHX_2, 0), UFTDI_DEV(FTDI, CCSPRIME8_5, 0), UFTDI_DEV(FTDI, CFA_631, 0), UFTDI_DEV(FTDI, CFA_632, 0), UFTDI_DEV(FTDI, CFA_633, 0), UFTDI_DEV(FTDI, CFA_634, 0), UFTDI_DEV(FTDI, CFA_635, 0), UFTDI_DEV(FTDI, CHAMSYS_24_MASTER_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_MAXI_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_MEDIA_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_MIDI_TIMECODE, 0), UFTDI_DEV(FTDI, CHAMSYS_MINI_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_PC_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_USB_DMX, 0), UFTDI_DEV(FTDI, CHAMSYS_WING, 0), UFTDI_DEV(FTDI, COM4SM, 0), UFTDI_DEV(FTDI, CONVERTER_0, 0), UFTDI_DEV(FTDI, CONVERTER_1, 0), UFTDI_DEV(FTDI, CONVERTER_2, 0), UFTDI_DEV(FTDI, CONVERTER_3, 0), UFTDI_DEV(FTDI, CONVERTER_4, 0), UFTDI_DEV(FTDI, CONVERTER_5, 0), UFTDI_DEV(FTDI, CONVERTER_6, 0), UFTDI_DEV(FTDI, CONVERTER_7, 0), UFTDI_DEV(FTDI, CTI_USB_MINI_485, 0), UFTDI_DEV(FTDI, CTI_USB_NANO_485, 0), UFTDI_DEV(FTDI, DMX4ALL, 0), UFTDI_DEV(FTDI, DOMINTELL_DGQG, 0), UFTDI_DEV(FTDI, DOMINTELL_DUSB, 0), UFTDI_DEV(FTDI, DOTEC, 0), UFTDI_DEV(FTDI, ECLO_COM_1WIRE, 0), UFTDI_DEV(FTDI, ECO_PRO_CDS, 0), UFTDI_DEV(FTDI, EISCOU, 0), UFTDI_DEV(FTDI, ELSTER_UNICOM, 0), UFTDI_DEV(FTDI, ELV_ALC8500, 0), UFTDI_DEV(FTDI, ELV_CLI7000, 0), UFTDI_DEV(FTDI, ELV_CSI8, 0), UFTDI_DEV(FTDI, ELV_EC3000, 0), UFTDI_DEV(FTDI, ELV_EM1000DL, 0), UFTDI_DEV(FTDI, ELV_EM1010PC, 0), UFTDI_DEV(FTDI, ELV_FEM, 0), UFTDI_DEV(FTDI, ELV_FHZ1000PC, 0), UFTDI_DEV(FTDI, ELV_FHZ1300PC, 0), UFTDI_DEV(FTDI, ELV_FM3RX, 0), UFTDI_DEV(FTDI, ELV_FS20SIG, 0), UFTDI_DEV(FTDI, ELV_HS485, 0), UFTDI_DEV(FTDI, ELV_KL100, 0), UFTDI_DEV(FTDI, ELV_MSM1, 0), UFTDI_DEV(FTDI, ELV_PCD200, 0), UFTDI_DEV(FTDI, ELV_PCK100, 0), UFTDI_DEV(FTDI, ELV_PPS7330, 0), UFTDI_DEV(FTDI, ELV_RFP500, 0), UFTDI_DEV(FTDI, ELV_T1100, 0), UFTDI_DEV(FTDI, ELV_TFD128, 0), UFTDI_DEV(FTDI, ELV_TFM100, 0), UFTDI_DEV(FTDI, ELV_TWS550, 0), UFTDI_DEV(FTDI, ELV_UAD8, 0), UFTDI_DEV(FTDI, ELV_UDA7, 0), UFTDI_DEV(FTDI, ELV_UDF77, 0), UFTDI_DEV(FTDI, ELV_UIO88, 0), UFTDI_DEV(FTDI, ELV_ULA200, 0), UFTDI_DEV(FTDI, ELV_UM100, 0), UFTDI_DEV(FTDI, ELV_UMS100, 0), UFTDI_DEV(FTDI, ELV_UO100, 0), UFTDI_DEV(FTDI, ELV_UR100, 0), UFTDI_DEV(FTDI, ELV_USI2, 0), UFTDI_DEV(FTDI, ELV_USR, 0), UFTDI_DEV(FTDI, ELV_UTP8, 0), UFTDI_DEV(FTDI, ELV_WS300PC, 0), UFTDI_DEV(FTDI, ELV_WS444PC, 0), UFTDI_DEV(FTDI, ELV_WS500, 0), UFTDI_DEV(FTDI, ELV_WS550, 0), UFTDI_DEV(FTDI, ELV_WS777, 0), UFTDI_DEV(FTDI, ELV_WS888, 0), UFTDI_DEV(FTDI, EMCU2D, 0), UFTDI_DEV(FTDI, EMCU2H, 0), UFTDI_DEV(FTDI, FUTURE_0, 0), UFTDI_DEV(FTDI, FUTURE_1, 0), UFTDI_DEV(FTDI, FUTURE_2, 0), UFTDI_DEV(FTDI, GAMMASCOUT, 0), UFTDI_DEV(FTDI, GENERIC, 0), UFTDI_DEV(FTDI, GUDEADS_E808, 0), UFTDI_DEV(FTDI, GUDEADS_E809, 0), UFTDI_DEV(FTDI, GUDEADS_E80A, 0), UFTDI_DEV(FTDI, GUDEADS_E80B, 0), UFTDI_DEV(FTDI, GUDEADS_E80C, 0), UFTDI_DEV(FTDI, GUDEADS_E80D, 0), UFTDI_DEV(FTDI, GUDEADS_E80E, 0), UFTDI_DEV(FTDI, GUDEADS_E80F, 0), UFTDI_DEV(FTDI, GUDEADS_E88D, 0), UFTDI_DEV(FTDI, GUDEADS_E88E, 0), UFTDI_DEV(FTDI, GUDEADS_E88F, 0), UFTDI_DEV(FTDI, HD_RADIO, 0), UFTDI_DEV(FTDI, HO720, 0), UFTDI_DEV(FTDI, HO730, 0), UFTDI_DEV(FTDI, HO820, 0), UFTDI_DEV(FTDI, HO870, 0), UFTDI_DEV(FTDI, IBS_APP70, 0), UFTDI_DEV(FTDI, IBS_PCMCIA, 0), UFTDI_DEV(FTDI, IBS_PEDO, 0), UFTDI_DEV(FTDI, IBS_PICPRO, 0), UFTDI_DEV(FTDI, IBS_PK1, 0), UFTDI_DEV(FTDI, IBS_PROD, 0), UFTDI_DEV(FTDI, IBS_RS232MON, 0), UFTDI_DEV(FTDI, IBS_US485, 0), UFTDI_DEV(FTDI, IPLUS, 0), UFTDI_DEV(FTDI, IPLUS2, 0), UFTDI_DEV(FTDI, IRTRANS, 0), UFTDI_DEV(FTDI, KBS, 0), UFTDI_DEV(FTDI, KTLINK, 0), UFTDI_DEV(FTDI, LENZ_LIUSB, 0), UFTDI_DEV(FTDI, LK202, 0), UFTDI_DEV(FTDI, LK204, 0), UFTDI_DEV(FTDI, LM3S_DEVEL_BOARD, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, LM3S_EVAL_BOARD, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, LM3S_ICDI_B_BOARD, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, MASTERDEVEL2, 0), UFTDI_DEV(FTDI, MAXSTREAM, 0), UFTDI_DEV(FTDI, MHAM_DB9, 0), UFTDI_DEV(FTDI, MHAM_IC, 0), UFTDI_DEV(FTDI, MHAM_KW, 0), UFTDI_DEV(FTDI, MHAM_RS232, 0), UFTDI_DEV(FTDI, MHAM_Y6, 0), UFTDI_DEV(FTDI, MHAM_Y8, 0), UFTDI_DEV(FTDI, MHAM_Y9, 0), UFTDI_DEV(FTDI, MHAM_YS, 0), UFTDI_DEV(FTDI, MICRO_CHAMELEON, 0), UFTDI_DEV(FTDI, MTXORB_5, 0), UFTDI_DEV(FTDI, MTXORB_6, 0), UFTDI_DEV(FTDI, MX2_3, 0), UFTDI_DEV(FTDI, MX4_5, 0), UFTDI_DEV(FTDI, NXTCAM, 0), UFTDI_DEV(FTDI, OCEANIC, 0), UFTDI_DEV(FTDI, OOCDLINK, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, OPENDCC, 0), UFTDI_DEV(FTDI, OPENDCC_GATEWAY, 0), UFTDI_DEV(FTDI, OPENDCC_GBM, 0), UFTDI_DEV(FTDI, OPENDCC_SNIFFER, 0), UFTDI_DEV(FTDI, OPENDCC_THROTTLE, 0), UFTDI_DEV(FTDI, PCDJ_DAC2, 0), UFTDI_DEV(FTDI, PCMSFU, 0), UFTDI_DEV(FTDI, PERLE_ULTRAPORT, 0), UFTDI_DEV(FTDI, PHI_FISCO, 0), UFTDI_DEV(FTDI, PIEGROUP, 0), UFTDI_DEV(FTDI, PROPOX_JTAGCABLEII, 0), UFTDI_DEV(FTDI, R2000KU_TRUE_RNG, 0), UFTDI_DEV(FTDI, R2X0, 0), UFTDI_DEV(FTDI, RELAIS, 0), UFTDI_DEV(FTDI, REU_TINY, 0), UFTDI_DEV(FTDI, RMP200, 0), UFTDI_DEV(FTDI, RM_CANVIEW, 0), UFTDI_DEV(FTDI, RRCIRKITS_LOCOBUFFER, 0), UFTDI_DEV(FTDI, SCIENCESCOPE_HS_LOGBOOK, 0), UFTDI_DEV(FTDI, SCIENCESCOPE_LOGBOOKML, 0), UFTDI_DEV(FTDI, SCIENCESCOPE_LS_LOGBOOK, 0), UFTDI_DEV(FTDI, SCS_DEVICE_0, 0), UFTDI_DEV(FTDI, SCS_DEVICE_1, 0), UFTDI_DEV(FTDI, SCS_DEVICE_2, 0), UFTDI_DEV(FTDI, SCS_DEVICE_3, 0), UFTDI_DEV(FTDI, SCS_DEVICE_4, 0), UFTDI_DEV(FTDI, SCS_DEVICE_5, 0), UFTDI_DEV(FTDI, SCS_DEVICE_6, 0), UFTDI_DEV(FTDI, SCS_DEVICE_7, 0), UFTDI_DEV(FTDI, SCX8_USB_PHOENIX, 0), UFTDI_DEV(FTDI, SDMUSBQSS, 0), UFTDI_DEV(FTDI, SEMC_DSS20, 0), UFTDI_DEV(FTDI, SERIAL_2232C, UFTDI_JTAG_CHECK_STRING), UFTDI_DEV(FTDI, SERIAL_2232D, 0), UFTDI_DEV(FTDI, SERIAL_232RL, 0), UFTDI_DEV(FTDI, SERIAL_4232H, 0), UFTDI_DEV(FTDI, SERIAL_8U100AX, 0), UFTDI_DEV(FTDI, SERIAL_8U232AM, 0), UFTDI_DEV(FTDI, SERIAL_8U232AM4, 0), UFTDI_DEV(FTDI, SIGNALYZER_SH2, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, SIGNALYZER_SH4, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, SIGNALYZER_SLITE, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, SIGNALYZER_ST, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, SPECIAL_1, 0), UFTDI_DEV(FTDI, SPECIAL_3, 0), UFTDI_DEV(FTDI, SPECIAL_4, 0), UFTDI_DEV(FTDI, SPROG_II, 0), UFTDI_DEV(FTDI, SR_RADIO, 0), UFTDI_DEV(FTDI, SUUNTO_SPORTS, 0), UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13M, 0), UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13S, 0), UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13U, 0), UFTDI_DEV(FTDI, TAVIR_STK500, 0), UFTDI_DEV(FTDI, TERATRONIK_D2XX, 0), UFTDI_DEV(FTDI, TERATRONIK_VCP, 0), UFTDI_DEV(FTDI, THORLABS, 0), UFTDI_DEV(FTDI, TNC_X, 0), UFTDI_DEV(FTDI, TTUSB, 0), UFTDI_DEV(FTDI, TURTELIZER2, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, UOPTBR, 0), UFTDI_DEV(FTDI, USBSERIAL, 0), UFTDI_DEV(FTDI, USBX_707, 0), UFTDI_DEV(FTDI, USB_UIRT, 0), UFTDI_DEV(FTDI, USINT_CAT, 0), UFTDI_DEV(FTDI, USINT_RS232, 0), UFTDI_DEV(FTDI, USINT_WKEY, 0), UFTDI_DEV(FTDI, VARDAAN, 0), UFTDI_DEV(FTDI, VNHCPCUSB_D, 0), UFTDI_DEV(FTDI, WESTREX_MODEL_777, 0), UFTDI_DEV(FTDI, WESTREX_MODEL_8900F, 0), UFTDI_DEV(FTDI, XDS100V2, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, XDS100V3, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, XF_547, 0), UFTDI_DEV(FTDI, XF_640, 0), UFTDI_DEV(FTDI, XF_642, 0), UFTDI_DEV(FTDI, XM_RADIO, 0), UFTDI_DEV(FTDI, YEI_SERVOCENTER31, 0), UFTDI_DEV(GNOTOMETRICS, USB, 0), UFTDI_DEV(ICOM, SP1, 0), UFTDI_DEV(ICOM, OPC_U_UC, 0), UFTDI_DEV(ICOM, RP2C1, 0), UFTDI_DEV(ICOM, RP2C2, 0), UFTDI_DEV(ICOM, RP2D, 0), UFTDI_DEV(ICOM, RP2KVR, 0), UFTDI_DEV(ICOM, RP2KVT, 0), UFTDI_DEV(ICOM, RP2VR, 0), UFTDI_DEV(ICOM, RP2VT, 0), UFTDI_DEV(ICOM, RP4KVR, 0), UFTDI_DEV(ICOM, RP4KVT, 0), UFTDI_DEV(IDTECH, IDT1221U, 0), UFTDI_DEV(INTERBIOMETRICS, IOBOARD, 0), UFTDI_DEV(INTERBIOMETRICS, MINI_IOBOARD, 0), UFTDI_DEV(INTREPIDCS, NEOVI, 0), UFTDI_DEV(INTREPIDCS, VALUECAN, 0), UFTDI_DEV(IONICS, PLUGCOMPUTER, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(JETI, SPC1201, 0), UFTDI_DEV(KOBIL, CONV_B1, 0), UFTDI_DEV(KOBIL, CONV_KAAN, 0), UFTDI_DEV(LARSENBRUSGAARD, ALTITRACK, 0), UFTDI_DEV(MARVELL, SHEEVAPLUG, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0100, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0101, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0102, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0103, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0104, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0105, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0106, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0107, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0108, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0109, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0110, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0111, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0112, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0113, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0114, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0115, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0116, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0117, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0118, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0119, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0120, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0121, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0122, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0123, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0124, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0125, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0126, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0128, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0129, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0130, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0131, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0132, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0133, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0134, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0135, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0136, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0137, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0138, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0139, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0140, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0141, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0142, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0143, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0144, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0145, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0146, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0147, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0148, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0149, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0150, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0151, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0152, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0159, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0160, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0161, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0162, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0163, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0164, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0165, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0166, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0167, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0168, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0169, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0170, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0171, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0172, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0173, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0174, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0175, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0176, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0177, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0178, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0179, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0180, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0181, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0182, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0183, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0184, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0185, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0186, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0187, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0188, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0189, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0190, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0191, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0192, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0193, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0194, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0195, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0196, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0197, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0198, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0199, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01ED, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FF, 0), UFTDI_DEV(MATRIXORBITAL, MOUA, 0), UFTDI_DEV(MELCO, PCOPRS1, 0), UFTDI_DEV(METAGEEK, TELLSTICK, 0), UFTDI_DEV(MOBILITY, USB_SERIAL, 0), UFTDI_DEV(OLIMEX, ARM_USB_OCD, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(OLIMEX, ARM_USB_OCD_H, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(OPTO, CRD7734, 0), UFTDI_DEV(OPTO, CRD7734_1, 0), UFTDI_DEV(PAPOUCH, AD4USB, 0), UFTDI_DEV(PAPOUCH, AP485, 0), UFTDI_DEV(PAPOUCH, AP485_2, 0), UFTDI_DEV(PAPOUCH, DRAK5, 0), UFTDI_DEV(PAPOUCH, DRAK6, 0), UFTDI_DEV(PAPOUCH, GMSR, 0), UFTDI_DEV(PAPOUCH, GMUX, 0), UFTDI_DEV(PAPOUCH, IRAMP, 0), UFTDI_DEV(PAPOUCH, LEC, 0), UFTDI_DEV(PAPOUCH, MU, 0), UFTDI_DEV(PAPOUCH, QUIDO10X1, 0), UFTDI_DEV(PAPOUCH, QUIDO2X16, 0), UFTDI_DEV(PAPOUCH, QUIDO2X2, 0), UFTDI_DEV(PAPOUCH, QUIDO30X3, 0), UFTDI_DEV(PAPOUCH, QUIDO3X32, 0), UFTDI_DEV(PAPOUCH, QUIDO4X4, 0), UFTDI_DEV(PAPOUCH, QUIDO60X3, 0), UFTDI_DEV(PAPOUCH, QUIDO8X8, 0), UFTDI_DEV(PAPOUCH, SB232, 0), UFTDI_DEV(PAPOUCH, SB422, 0), UFTDI_DEV(PAPOUCH, SB422_2, 0), UFTDI_DEV(PAPOUCH, SB485, 0), UFTDI_DEV(PAPOUCH, SB485C, 0), UFTDI_DEV(PAPOUCH, SB485S, 0), UFTDI_DEV(PAPOUCH, SB485_2, 0), UFTDI_DEV(PAPOUCH, SIMUKEY, 0), UFTDI_DEV(PAPOUCH, TMU, 0), UFTDI_DEV(PAPOUCH, UPSUSB, 0), UFTDI_DEV(POSIFLEX, PP7000, 0), UFTDI_DEV(QIHARDWARE, JTAGSERIAL, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(RATOC, REXUSB60F, 0), UFTDI_DEV(RTSYSTEMS, CT29B, 0), UFTDI_DEV(RTSYSTEMS, SERIAL_VX7, 0), UFTDI_DEV(SEALEVEL, 2101, 0), UFTDI_DEV(SEALEVEL, 2102, 0), UFTDI_DEV(SEALEVEL, 2103, 0), UFTDI_DEV(SEALEVEL, 2104, 0), UFTDI_DEV(SEALEVEL, 2106, 0), UFTDI_DEV(SEALEVEL, 2201_1, 0), UFTDI_DEV(SEALEVEL, 2201_2, 0), UFTDI_DEV(SEALEVEL, 2202_1, 0), UFTDI_DEV(SEALEVEL, 2202_2, 0), UFTDI_DEV(SEALEVEL, 2203_1, 0), UFTDI_DEV(SEALEVEL, 2203_2, 0), UFTDI_DEV(SEALEVEL, 2401_1, 0), UFTDI_DEV(SEALEVEL, 2401_2, 0), UFTDI_DEV(SEALEVEL, 2401_3, 0), UFTDI_DEV(SEALEVEL, 2401_4, 0), UFTDI_DEV(SEALEVEL, 2402_1, 0), UFTDI_DEV(SEALEVEL, 2402_2, 0), UFTDI_DEV(SEALEVEL, 2402_3, 0), UFTDI_DEV(SEALEVEL, 2402_4, 0), UFTDI_DEV(SEALEVEL, 2403_1, 0), UFTDI_DEV(SEALEVEL, 2403_2, 0), UFTDI_DEV(SEALEVEL, 2403_3, 0), UFTDI_DEV(SEALEVEL, 2403_4, 0), UFTDI_DEV(SEALEVEL, 2801_1, 0), UFTDI_DEV(SEALEVEL, 2801_2, 0), UFTDI_DEV(SEALEVEL, 2801_3, 0), UFTDI_DEV(SEALEVEL, 2801_4, 0), UFTDI_DEV(SEALEVEL, 2801_5, 0), UFTDI_DEV(SEALEVEL, 2801_6, 0), UFTDI_DEV(SEALEVEL, 2801_7, 0), UFTDI_DEV(SEALEVEL, 2801_8, 0), UFTDI_DEV(SEALEVEL, 2802_1, 0), UFTDI_DEV(SEALEVEL, 2802_2, 0), UFTDI_DEV(SEALEVEL, 2802_3, 0), UFTDI_DEV(SEALEVEL, 2802_4, 0), UFTDI_DEV(SEALEVEL, 2802_5, 0), UFTDI_DEV(SEALEVEL, 2802_6, 0), UFTDI_DEV(SEALEVEL, 2802_7, 0), UFTDI_DEV(SEALEVEL, 2802_8, 0), UFTDI_DEV(SEALEVEL, 2803_1, 0), UFTDI_DEV(SEALEVEL, 2803_2, 0), UFTDI_DEV(SEALEVEL, 2803_3, 0), UFTDI_DEV(SEALEVEL, 2803_4, 0), UFTDI_DEV(SEALEVEL, 2803_5, 0), UFTDI_DEV(SEALEVEL, 2803_6, 0), UFTDI_DEV(SEALEVEL, 2803_7, 0), UFTDI_DEV(SEALEVEL, 2803_8, 0), UFTDI_DEV(SIIG2, DK201, 0), UFTDI_DEV(SIIG2, US2308, 0), UFTDI_DEV(TESTO, USB_INTERFACE, 0), UFTDI_DEV(TML, USB_SERIAL, 0), UFTDI_DEV(TTI, QL355P, 0), UFTDI_DEV(UNKNOWN4, NF_RIC, 0), #undef UFTDI_DEV }; DRIVER_MODULE(uftdi, uhub, uftdi_driver, uftdi_devclass, NULL, NULL); MODULE_DEPEND(uftdi, ucom, 1, 1, 1); MODULE_DEPEND(uftdi, usb, 1, 1, 1); MODULE_VERSION(uftdi, 1); USB_PNP_HOST_INFO(uftdi_devs); /* * Jtag product name strings table. Some products have one or more interfaces * dedicated to jtag or gpio, but use a product ID that's the same as other * products which don't. They are marked with a flag in the table above, and * the following string table is checked for flagged products. The string check * is done with strstr(); in effect there is an implicit wildcard at the * beginning and end of each product name string in table. */ static const struct jtag_by_name { const char * product_name; uint32_t jtag_interfaces; } jtag_products_by_name[] = { /* TI Beaglebone and TI XDS100Vn jtag product line. */ {"XDS100V", UFTDI_JTAG_IFACE(0)}, }; /* * Set up a sysctl and tunable to en/disable the feature of skipping the * creation of tty devices for jtag interfaces. Enabled by default. */ static int skip_jtag_interfaces = 1; SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, skip_jtag_interfaces, CTLFLAG_RWTUN, &skip_jtag_interfaces, 1, "Skip creating tty devices for jtag interfaces"); static boolean_t is_jtag_interface(struct usb_attach_arg *uaa, const struct usb_device_id *id) { int i, iface_bit; const char * product_name; const struct jtag_by_name *jbn; /* We only allocate 8 flag bits for jtag interface flags. */ if (uaa->info.bIfaceIndex >= UFTDI_JTAG_IFACES_MAX) return (0); iface_bit = UFTDI_JTAG_IFACE(uaa->info.bIfaceIndex); /* * If requested, search the name strings table and use the interface * bits from that table when the product name string matches, else use * the jtag interface bits from the main ID table. */ if ((id->driver_info & UFTDI_JTAG_MASK) == UFTDI_JTAG_CHECK_STRING) { product_name = usb_get_product(uaa->device); for (i = 0; i < nitems(jtag_products_by_name); i++) { jbn = &jtag_products_by_name[i]; if (strstr(product_name, jbn->product_name) != NULL && (jbn->jtag_interfaces & iface_bit) != 0) return (1); } } else if ((id->driver_info & iface_bit) != 0) return (1); return (0); } /* * Set up softc fields whose value depends on the device type. * * Note that the 2232C and 2232D devices are the same for our purposes. In the * silicon the difference is that the D series has CPU FIFO mode and C doesn't. * I haven't found any way of determining the C/D difference from info provided * by the chip other than trying to set CPU FIFO mode and having it work or not. * * Due to a hardware bug, a 232B chip without an eeprom reports itself as a * 232A, but if the serial number is also zero we know it's really a 232B. */ static void uftdi_devtype_setup(struct uftdi_softc *sc, struct usb_attach_arg *uaa) { struct usb_device_descriptor *dd; sc->sc_bcdDevice = uaa->info.bcdDevice; switch (uaa->info.bcdDevice) { case 0x200: dd = usbd_get_device_descriptor(sc->sc_udev); if (dd->iSerialNumber == 0) { sc->sc_devtype = DEVT_232B; } else { sc->sc_devtype = DEVT_232A; } sc->sc_ucom.sc_portno = 0; break; case 0x400: sc->sc_devtype = DEVT_232B; sc->sc_ucom.sc_portno = 0; break; case 0x500: sc->sc_devtype = DEVT_2232D; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; case 0x600: sc->sc_devtype = DEVT_232R; sc->sc_ucom.sc_portno = 0; break; case 0x700: sc->sc_devtype = DEVT_2232H; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; case 0x800: sc->sc_devtype = DEVT_4232H; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; case 0x900: sc->sc_devtype = DEVT_232H; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; case 0x1000: sc->sc_devtype = DEVT_230X; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; default: if (uaa->info.bcdDevice < 0x200) { sc->sc_devtype = DEVT_SIO; sc->sc_hdrlen = 1; } else { sc->sc_devtype = DEVT_232R; device_printf(sc->sc_dev, "Warning: unknown FTDI " "device type, bcdDevice=0x%04x, assuming 232R\n", uaa->info.bcdDevice); } sc->sc_ucom.sc_portno = 0; break; } } static int uftdi_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); const struct usb_device_id *id; if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } if (uaa->info.bConfigIndex != UFTDI_CONFIG_INDEX) { return (ENXIO); } /* * Attach to all present interfaces unless this is a JTAG one, which * we leave for userland. */ id = usbd_lookup_id_by_info(uftdi_devs, sizeof(uftdi_devs), &uaa->info); if (id == NULL) return (ENXIO); if (skip_jtag_interfaces && is_jtag_interface(uaa, id)) { printf("%s: skipping JTAG interface #%d for '%s' at %u.%u\n", device_get_name(dev), uaa->info.bIfaceIndex, usb_get_product(uaa->device), usbd_get_bus_index(uaa->device), usbd_get_device_index(uaa->device)); return (ENXIO); } uaa->driver_info = id->driver_info; return (BUS_PROBE_SPECIFIC); } static int uftdi_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uftdi_softc *sc = device_get_softc(dev); int error; DPRINTF("\n"); sc->sc_udev = uaa->device; sc->sc_dev = dev; sc->sc_unit = device_get_unit(dev); sc->sc_bitmode = UFTDI_BITMODE_NONE; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "uftdi", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); uftdi_devtype_setup(sc, uaa); error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, uftdi_config, UFTDI_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB " "transfers failed\n"); goto detach; } /* clear stall at first run */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_WR]); usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_RD]); mtx_unlock(&sc->sc_mtx); /* set a valid "lcr" value */ sc->sc_last_lcr = (FTDI_SIO_SET_DATA_STOP_BITS_2 | FTDI_SIO_SET_DATA_PARITY_NONE | FTDI_SIO_SET_DATA_BITS(8)); error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &uftdi_callback, &sc->sc_mtx); if (error) { goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); return (0); /* success */ detach: uftdi_detach(dev); return (ENXIO); } static int uftdi_detach(device_t dev) { struct uftdi_softc *sc = device_get_softc(dev); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, UFTDI_N_TRANSFER); device_claim_softc(dev); uftdi_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(uftdi); static void uftdi_free_softc(struct uftdi_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void uftdi_free(struct ucom_softc *ucom) { uftdi_free_softc(ucom->sc_parent); } static void uftdi_cfg_open(struct ucom_softc *ucom) { /* * This do-nothing open routine exists for the sole purpose of this * DPRINTF() so that you can see the point at which open gets called * when debugging is enabled. */ - DPRINTF(""); + DPRINTF("\n"); } static void uftdi_cfg_close(struct ucom_softc *ucom) { /* * This do-nothing close routine exists for the sole purpose of this * DPRINTF() so that you can see the point at which close gets called * when debugging is enabled. */ - DPRINTF(""); + DPRINTF("\n"); } static void uftdi_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uftdi_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t pktlen; uint32_t buflen; uint8_t buf[1]; + DPRINTFN(3, "\n"); + switch (USB_GET_STATE(xfer)) { default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); } /* FALLTHROUGH */ case USB_ST_SETUP: case USB_ST_TRANSFERRED: /* * If output packets don't require headers (the common case) we * can just load the buffer up with payload bytes all at once. * Otherwise, loop to format packets into the buffer while there * is data available, and room for a packet header and at least * one byte of payload. * * NOTE: The FTDI chip doesn't accept zero length * packets. This cannot happen because the "pktlen" * will always be non-zero when "ucom_get_data()" * returns non-zero which we check below. */ pc = usbd_xfer_get_frame(xfer, 0); if (sc->sc_hdrlen == 0) { if (ucom_get_data(&sc->sc_ucom, pc, 0, UFTDI_OBUFSIZE, &buflen) == 0) break; } else { buflen = 0; while (buflen < UFTDI_OBUFSIZE - sc->sc_hdrlen - 1 && ucom_get_data(&sc->sc_ucom, pc, buflen + sc->sc_hdrlen, UFTDI_OPKTSIZE - sc->sc_hdrlen, &pktlen) != 0) { buf[0] = FTDI_OUT_TAG(pktlen, sc->sc_ucom.sc_portno); usbd_copy_in(pc, buflen, buf, 1); buflen += pktlen + sc->sc_hdrlen; } } if (buflen != 0) { usbd_xfer_set_frame_len(xfer, 0, buflen); usbd_transfer_submit(xfer); } break; } } static void uftdi_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uftdi_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint8_t buf[2]; uint8_t ftdi_msr; uint8_t msr; uint8_t lsr; int buflen; int pktlen; int pktmax; int offset; + DPRINTFN(3, "\n"); + usbd_xfer_status(xfer, &buflen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (buflen < UFTDI_IHDRSIZE) goto tr_setup; pc = usbd_xfer_get_frame(xfer, 0); pktmax = xfer->max_packet_size - UFTDI_IHDRSIZE; lsr = 0; msr = 0; offset = 0; /* * Extract packet headers and payload bytes from the buffer. * Feed payload bytes to ucom/tty layer; OR-accumulate header * status bits which are transient and could toggle with each * packet. After processing all packets in the buffer, process * the accumulated transient MSR and LSR values along with the * non-transient bits from the last packet header. */ while (buflen >= UFTDI_IHDRSIZE) { usbd_copy_out(pc, offset, buf, UFTDI_IHDRSIZE); offset += UFTDI_IHDRSIZE; buflen -= UFTDI_IHDRSIZE; lsr |= FTDI_GET_LSR(buf); if (FTDI_GET_MSR(buf) & FTDI_SIO_RI_MASK) msr |= SER_RI; pktlen = min(buflen, pktmax); if (pktlen != 0) { ucom_put_data(&sc->sc_ucom, pc, offset, pktlen); offset += pktlen; buflen -= pktlen; } } ftdi_msr = FTDI_GET_MSR(buf); if (ftdi_msr & FTDI_SIO_CTS_MASK) msr |= SER_CTS; if (ftdi_msr & FTDI_SIO_DSR_MASK) msr |= SER_DSR; if (ftdi_msr & FTDI_SIO_RI_MASK) msr |= SER_RI; if (ftdi_msr & FTDI_SIO_RLSD_MASK) msr |= SER_DCD; if ((sc->sc_msr != msr) || ((sc->sc_lsr & FTDI_LSR_MASK) != (lsr & FTDI_LSR_MASK))) { DPRINTF("status change msr=0x%02x (0x%02x) " "lsr=0x%02x (0x%02x)\n", msr, sc->sc_msr, lsr, sc->sc_lsr); sc->sc_msr = msr; sc->sc_lsr = lsr; ucom_status_change(&sc->sc_ucom); } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } return; } } static void uftdi_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct uftdi_softc *sc = ucom->sc_parent; uint16_t wIndex = ucom->sc_portno; uint16_t wValue; struct usb_device_request req; + DPRINTFN(2, "DTR=%u\n", onoff); + wValue = onoff ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_MODEM_CTRL; USETW(req.wValue, wValue); USETW(req.wIndex, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void uftdi_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct uftdi_softc *sc = ucom->sc_parent; uint16_t wIndex = ucom->sc_portno; uint16_t wValue; struct usb_device_request req; + DPRINTFN(2, "RTS=%u\n", onoff); + wValue = onoff ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_MODEM_CTRL; USETW(req.wValue, wValue); USETW(req.wIndex, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void uftdi_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct uftdi_softc *sc = ucom->sc_parent; uint16_t wIndex = ucom->sc_portno; uint16_t wValue; struct usb_device_request req; + DPRINTFN(2, "BREAK=%u\n", onoff); + if (onoff) { sc->sc_last_lcr |= FTDI_SIO_SET_BREAK; } else { sc->sc_last_lcr &= ~FTDI_SIO_SET_BREAK; } wValue = sc->sc_last_lcr; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_DATA; USETW(req.wValue, wValue); USETW(req.wIndex, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } /* * Return true if the given speed is within operational tolerance of the target * speed. FTDI recommends that the hardware speed be within 3% of nominal. */ static inline boolean_t uftdi_baud_within_tolerance(uint64_t speed, uint64_t target) { return ((speed >= (target * 100) / 103) && (speed <= (target * 100) / 97)); } static int uftdi_sio_encode_baudrate(struct uftdi_softc *sc, speed_t speed, struct uftdi_param_config *cfg) { u_int i; const speed_t sio_speeds[] = { 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 }; /* * The original SIO chips were limited to a small choice of speeds * listed in an internal table of speeds chosen by an index value. */ for (i = 0; i < nitems(sio_speeds); ++i) { if (speed == sio_speeds[i]) { cfg->baud_lobits = i; cfg->baud_hibits = 0; return (0); } } return (ERANGE); } static int uftdi_encode_baudrate(struct uftdi_softc *sc, speed_t speed, struct uftdi_param_config *cfg) { static const uint8_t encoded_fraction[8] = {0, 3, 2, 4, 1, 5, 6, 7}; static const uint8_t roundoff_232a[16] = { 0, 1, 0, 1, 0, -1, 2, 1, 0, -1, -2, -3, 4, 3, 2, 1, }; uint32_t clk, divisor, fastclk_flag, frac, hwspeed; /* * If this chip has the fast clock capability and the speed is within * range, use the 12MHz clock, otherwise the standard clock is 3MHz. */ if ((sc->sc_devflags & DEVF_BAUDCLK_12M) && speed >= 1200) { clk = 12000000; fastclk_flag = (1 << 17); } else { clk = 3000000; fastclk_flag = 0; } /* * Make sure the requested speed is reachable with the available clock * and a 14-bit divisor. */ if (speed < (clk >> 14) || speed > clk) return (ERANGE); /* * Calculate the divisor, initially yielding a fixed point number with a * 4-bit (1/16ths) fraction, then round it to the nearest fraction the * hardware can handle. When the integral part of the divisor is * greater than one, the fractional part is in 1/8ths of the base clock. * The FT8U232AM chips can handle only 0.125, 0.250, and 0.5 fractions. * Later chips can handle all 1/8th fractions. * * If the integral part of the divisor is 1, a special rule applies: the * fractional part can only be .0 or .5 (this is a limitation of the * hardware). We handle this by truncating the fraction rather than * rounding, because this only applies to the two fastest speeds the * chip can achieve and rounding doesn't matter, either you've asked for * that exact speed or you've asked for something the chip can't do. * * For the FT8U232AM chips, use a roundoff table to adjust the result * to the nearest 1/8th fraction that is supported by the hardware, * leaving a fixed-point number with a 3-bit fraction which exactly * represents the math the hardware divider will do. For later-series * chips that support all 8 fractional divisors, just round 16ths to * 8ths by adding 1 and dividing by 2. */ divisor = (clk << 4) / speed; if ((divisor & 0xf) == 1) divisor &= 0xfffffff8; else if (sc->sc_devtype == DEVT_232A) divisor += roundoff_232a[divisor & 0x0f]; else divisor += 1; /* Rounds odd 16ths up to next 8th. */ divisor >>= 1; /* * Ensure the resulting hardware speed will be within operational * tolerance (within 3% of nominal). */ hwspeed = (clk << 3) / divisor; if (!uftdi_baud_within_tolerance(hwspeed, speed)) return (ERANGE); /* * Re-pack the divisor into hardware format. The lower 14-bits hold the * integral part, while the upper bits specify the fraction by indexing * a table of fractions within the hardware which is laid out as: * {0.0, 0.5, 0.25, 0.125, 0.325, 0.625, 0.725, 0.875} * The A-series chips only have the first four table entries; the * roundoff table logic above ensures that the fractional part for those * chips will be one of the first four values. * * When the divisor is 1 a special encoding applies: 1.0 is encoded as * 0.0, and 1.5 is encoded as 1.0. The rounding logic above has already * ensured that the fraction is either .0 or .5 if the integral is 1. */ frac = divisor & 0x07; divisor >>= 3; if (divisor == 1) { if (frac == 0) divisor = 0; /* 1.0 becomes 0.0 */ else frac = 0; /* 1.5 becomes 1.0 */ } divisor |= (encoded_fraction[frac] << 14) | fastclk_flag; cfg->baud_lobits = (uint16_t)divisor; cfg->baud_hibits = (uint16_t)(divisor >> 16); /* * If this chip requires the baud bits to be in the high byte of the * index word, move the bits up to that location. */ if (sc->sc_devflags & DEVF_BAUDBITS_HINDEX) { cfg->baud_hibits <<= 8; } return (0); } static int uftdi_set_parm_soft(struct ucom_softc *ucom, struct termios *t, struct uftdi_param_config *cfg) { struct uftdi_softc *sc = ucom->sc_parent; int err; memset(cfg, 0, sizeof(*cfg)); if (sc->sc_devtype == DEVT_SIO) err = uftdi_sio_encode_baudrate(sc, t->c_ospeed, cfg); else err = uftdi_encode_baudrate(sc, t->c_ospeed, cfg); if (err != 0) return (err); if (t->c_cflag & CSTOPB) cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_2; else cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_1; if (t->c_cflag & PARENB) { if (t->c_cflag & PARODD) { cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_ODD; } else { cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_EVEN; } } else { cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_NONE; } switch (t->c_cflag & CSIZE) { case CS5: cfg->lcr |= FTDI_SIO_SET_DATA_BITS(5); break; case CS6: cfg->lcr |= FTDI_SIO_SET_DATA_BITS(6); break; case CS7: cfg->lcr |= FTDI_SIO_SET_DATA_BITS(7); break; case CS8: cfg->lcr |= FTDI_SIO_SET_DATA_BITS(8); break; } if (t->c_cflag & CRTSCTS) { cfg->v_flow = FTDI_SIO_RTS_CTS_HS; } else if (t->c_iflag & (IXON | IXOFF)) { cfg->v_flow = FTDI_SIO_XON_XOFF_HS; cfg->v_start = t->c_cc[VSTART]; cfg->v_stop = t->c_cc[VSTOP]; } else { cfg->v_flow = FTDI_SIO_DISABLE_FLOW_CTRL; } return (0); } static int uftdi_pre_param(struct ucom_softc *ucom, struct termios *t) { struct uftdi_param_config cfg; DPRINTF("\n"); return (uftdi_set_parm_soft(ucom, t, &cfg)); } static void uftdi_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct uftdi_softc *sc = ucom->sc_parent; uint16_t wIndex = ucom->sc_portno; struct uftdi_param_config cfg; struct usb_device_request req; + DPRINTF("\n"); + if (uftdi_set_parm_soft(ucom, t, &cfg)) { /* should not happen */ return; } sc->sc_last_lcr = cfg.lcr; - DPRINTF("\n"); - req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_BAUD_RATE; USETW(req.wValue, cfg.baud_lobits); USETW(req.wIndex, cfg.baud_hibits | wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_DATA; USETW(req.wValue, cfg.lcr); USETW(req.wIndex, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_FLOW_CTRL; USETW2(req.wValue, cfg.v_stop, cfg.v_start); USETW2(req.wIndex, cfg.v_flow, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void uftdi_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct uftdi_softc *sc = ucom->sc_parent; - DPRINTF("msr=0x%02x lsr=0x%02x\n", - sc->sc_msr, sc->sc_lsr); + DPRINTFN(3, "msr=0x%02x lsr=0x%02x\n", sc->sc_msr, sc->sc_lsr); *msr = sc->sc_msr; *lsr = sc->sc_lsr; } static int uftdi_reset(struct ucom_softc *ucom, int reset_type) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; + DPRINTFN(2, "\n"); + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_RESET; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); USETW(req.wValue, reset_type); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static int uftdi_set_bitmode(struct ucom_softc *ucom, uint8_t bitmode, uint8_t iomask) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; int rv; + DPRINTFN(2, "\n"); + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_BITMODE; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); if (bitmode == UFTDI_BITMODE_NONE) USETW2(req.wValue, 0, 0); else USETW2(req.wValue, (1 << bitmode), iomask); rv = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL); if (rv == USB_ERR_NORMAL_COMPLETION) sc->sc_bitmode = bitmode; return (rv); } static int uftdi_get_bitmode(struct ucom_softc *ucom, uint8_t *bitmode, uint8_t *iomask) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; + DPRINTFN(2, "\n"); + req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = FTDI_SIO_GET_BITMODE; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 1); USETW(req.wValue, 0); *bitmode = sc->sc_bitmode; return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, iomask)); } static int uftdi_set_latency(struct ucom_softc *ucom, int latency) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; + DPRINTFN(2, "\n"); + if (latency < 0 || latency > 255) return (USB_ERR_INVAL); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_LATENCY; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); USETW2(req.wValue, 0, latency); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static int uftdi_get_latency(struct ucom_softc *ucom, int *latency) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; usb_error_t err; uint8_t buf; + DPRINTFN(2, "\n"); + req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = FTDI_SIO_GET_LATENCY; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 1); USETW(req.wValue, 0); err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &buf); *latency = buf; return (err); } static int uftdi_set_event_char(struct ucom_softc *ucom, int echar) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; uint8_t enable; + DPRINTFN(2, "\n"); + enable = (echar == -1) ? 0 : 1; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_EVENT_CHAR; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); USETW2(req.wValue, enable, echar & 0xff); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static int uftdi_set_error_char(struct ucom_softc *ucom, int echar) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; uint8_t enable; + DPRINTFN(2, "\n"); + enable = (echar == -1) ? 0 : 1; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_ERROR_CHAR; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); USETW2(req.wValue, enable, echar & 0xff); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static int uftdi_read_eeprom(struct ucom_softc *ucom, struct uftdi_eeio *eeio) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; usb_error_t err; uint16_t widx, wlength, woffset; + DPRINTFN(3, "\n"); + /* Offset and length must both be evenly divisible by two. */ if ((eeio->offset | eeio->length) & 0x01) return (EINVAL); woffset = eeio->offset / 2U; wlength = eeio->length / 2U; for (widx = 0; widx < wlength; widx++) { req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = FTDI_SIO_READ_EEPROM; USETW(req.wIndex, widx + woffset); USETW(req.wLength, 2); USETW(req.wValue, 0); err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &eeio->data[widx]); if (err != USB_ERR_NORMAL_COMPLETION) return (err); } return (USB_ERR_NORMAL_COMPLETION); } static int uftdi_write_eeprom(struct ucom_softc *ucom, struct uftdi_eeio *eeio) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; usb_error_t err; uint16_t widx, wlength, woffset; + DPRINTFN(3, "\n"); + /* Offset and length must both be evenly divisible by two. */ if ((eeio->offset | eeio->length) & 0x01) return (EINVAL); woffset = eeio->offset / 2U; wlength = eeio->length / 2U; for (widx = 0; widx < wlength; widx++) { req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_WRITE_EEPROM; USETW(req.wIndex, widx + woffset); USETW(req.wLength, 0); USETW(req.wValue, eeio->data[widx]); err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL); if (err != USB_ERR_NORMAL_COMPLETION) return (err); } return (USB_ERR_NORMAL_COMPLETION); } static int uftdi_erase_eeprom(struct ucom_softc *ucom, int confirmation) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; usb_error_t err; + DPRINTFN(2, "\n"); + /* Small effort to prevent accidental erasure. */ if (confirmation != UFTDI_CONFIRM_ERASE) return (EINVAL); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_ERASE_EEPROM; USETW(req.wIndex, 0); USETW(req.wLength, 0); USETW(req.wValue, 0); err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL); return (err); } static int uftdi_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data, int flag, struct thread *td) { struct uftdi_softc *sc = ucom->sc_parent; int err; struct uftdi_bitmode * mode; - - DPRINTF("portno: %d cmd: %#x\n", ucom->sc_portno, cmd); switch (cmd) { case UFTDIIOC_RESET_IO: case UFTDIIOC_RESET_RX: case UFTDIIOC_RESET_TX: err = uftdi_reset(ucom, cmd == UFTDIIOC_RESET_IO ? FTDI_SIO_RESET_SIO : (cmd == UFTDIIOC_RESET_RX ? FTDI_SIO_RESET_PURGE_RX : FTDI_SIO_RESET_PURGE_TX)); break; case UFTDIIOC_SET_BITMODE: mode = (struct uftdi_bitmode *)data; err = uftdi_set_bitmode(ucom, mode->mode, mode->iomask); break; case UFTDIIOC_GET_BITMODE: mode = (struct uftdi_bitmode *)data; err = uftdi_get_bitmode(ucom, &mode->mode, &mode->iomask); break; case UFTDIIOC_SET_LATENCY: err = uftdi_set_latency(ucom, *((int *)data)); break; case UFTDIIOC_GET_LATENCY: err = uftdi_get_latency(ucom, (int *)data); break; case UFTDIIOC_SET_ERROR_CHAR: err = uftdi_set_error_char(ucom, *(int *)data); break; case UFTDIIOC_SET_EVENT_CHAR: err = uftdi_set_event_char(ucom, *(int *)data); break; case UFTDIIOC_GET_HWREV: *(int *)data = sc->sc_bcdDevice; err = 0; break; case UFTDIIOC_READ_EEPROM: err = uftdi_read_eeprom(ucom, (struct uftdi_eeio *)data); break; case UFTDIIOC_WRITE_EEPROM: err = uftdi_write_eeprom(ucom, (struct uftdi_eeio *)data); break; case UFTDIIOC_ERASE_EEPROM: err = uftdi_erase_eeprom(ucom, *(int *)data); break; default: return (ENOIOCTL); } if (err != USB_ERR_NORMAL_COMPLETION) return (EIO); return (0); } static void uftdi_start_read(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_RD]); } static void uftdi_stop_read(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_RD]); } static void uftdi_start_write(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_WR]); } static void uftdi_stop_write(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_WR]); } static void uftdi_poll(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer, UFTDI_N_TRANSFER); } Index: projects/release-pkg/sys/dev/usb/wlan/if_rsu.c =================================================================== --- projects/release-pkg/sys/dev/usb/wlan/if_rsu.c (revision 297604) +++ projects/release-pkg/sys/dev/usb/wlan/if_rsu.c (revision 297605) @@ -1,2944 +1,2946 @@ /* $OpenBSD: if_rsu.c,v 1.17 2013/04/15 09:23:01 mglocker Exp $ */ /*- * Copyright (c) 2010 Damien Bergamini * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); /* * Driver for Realtek RTL8188SU/RTL8191SU/RTL8192SU. * * TODO: * o h/w crypto * o hostap / ibss / mesh * o sensible RSSI levels * o power-save operation */ #include "opt_wlan.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 #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR rsu_debug #include #include #ifdef USB_DEBUG static int rsu_debug = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, rsu, CTLFLAG_RW, 0, "USB rsu"); SYSCTL_INT(_hw_usb_rsu, OID_AUTO, debug, CTLFLAG_RWTUN, &rsu_debug, 0, "Debug level"); #define RSU_DPRINTF(_sc, _flg, ...) \ do \ if (((_flg) == (RSU_DEBUG_ANY)) || (rsu_debug & (_flg))) \ device_printf((_sc)->sc_dev, __VA_ARGS__); \ while (0) #else #define RSU_DPRINTF(_sc, _flg, ...) #endif static int rsu_enable_11n = 1; TUNABLE_INT("hw.usb.rsu.enable_11n", &rsu_enable_11n); #define RSU_DEBUG_ANY 0xffffffff #define RSU_DEBUG_TX 0x00000001 #define RSU_DEBUG_RX 0x00000002 #define RSU_DEBUG_RESET 0x00000004 #define RSU_DEBUG_CALIB 0x00000008 #define RSU_DEBUG_STATE 0x00000010 #define RSU_DEBUG_SCAN 0x00000020 #define RSU_DEBUG_FWCMD 0x00000040 #define RSU_DEBUG_TXDONE 0x00000080 #define RSU_DEBUG_FW 0x00000100 #define RSU_DEBUG_FWDBG 0x00000200 #define RSU_DEBUG_AMPDU 0x00000400 static const STRUCT_USB_HOST_ID rsu_devs[] = { #define RSU_HT_NOT_SUPPORTED 0 #define RSU_HT_SUPPORTED 1 #define RSU_DEV_HT(v,p) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, \ RSU_HT_SUPPORTED) } #define RSU_DEV(v,p) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, \ RSU_HT_NOT_SUPPORTED) } RSU_DEV(ASUS, RTL8192SU), RSU_DEV(AZUREWAVE, RTL8192SU_4), RSU_DEV_HT(ACCTON, RTL8192SU), RSU_DEV_HT(ASUS, USBN10), RSU_DEV_HT(AZUREWAVE, RTL8192SU_1), RSU_DEV_HT(AZUREWAVE, RTL8192SU_2), RSU_DEV_HT(AZUREWAVE, RTL8192SU_3), RSU_DEV_HT(AZUREWAVE, RTL8192SU_5), RSU_DEV_HT(BELKIN, RTL8192SU_1), RSU_DEV_HT(BELKIN, RTL8192SU_2), RSU_DEV_HT(BELKIN, RTL8192SU_3), RSU_DEV_HT(CONCEPTRONIC2, RTL8192SU_1), RSU_DEV_HT(CONCEPTRONIC2, RTL8192SU_2), RSU_DEV_HT(CONCEPTRONIC2, RTL8192SU_3), RSU_DEV_HT(COREGA, RTL8192SU), RSU_DEV_HT(DLINK2, DWA131A1), RSU_DEV_HT(DLINK2, RTL8192SU_1), RSU_DEV_HT(DLINK2, RTL8192SU_2), RSU_DEV_HT(EDIMAX, RTL8192SU_1), RSU_DEV_HT(EDIMAX, RTL8192SU_2), RSU_DEV_HT(EDIMAX, EW7622UMN), RSU_DEV_HT(GUILLEMOT, HWGUN54), RSU_DEV_HT(GUILLEMOT, HWNUM300), RSU_DEV_HT(HAWKING, RTL8192SU_1), RSU_DEV_HT(HAWKING, RTL8192SU_2), RSU_DEV_HT(PLANEX2, GWUSNANO), RSU_DEV_HT(REALTEK, RTL8171), RSU_DEV_HT(REALTEK, RTL8172), RSU_DEV_HT(REALTEK, RTL8173), RSU_DEV_HT(REALTEK, RTL8174), RSU_DEV_HT(REALTEK, RTL8192SU), RSU_DEV_HT(REALTEK, RTL8712), RSU_DEV_HT(REALTEK, RTL8713), RSU_DEV_HT(SENAO, RTL8192SU_1), RSU_DEV_HT(SENAO, RTL8192SU_2), RSU_DEV_HT(SITECOMEU, WL349V1), RSU_DEV_HT(SITECOMEU, WL353), RSU_DEV_HT(SWEEX2, LW154), RSU_DEV_HT(TRENDNET, TEW646UBH), #undef RSU_DEV_HT #undef RSU_DEV }; static device_probe_t rsu_match; static device_attach_t rsu_attach; static device_detach_t rsu_detach; static usb_callback_t rsu_bulk_tx_callback_be_bk; static usb_callback_t rsu_bulk_tx_callback_vi_vo; static usb_callback_t rsu_bulk_tx_callback_h2c; static usb_callback_t rsu_bulk_rx_callback; static usb_error_t rsu_do_request(struct rsu_softc *, struct usb_device_request *, void *); static struct ieee80211vap * rsu_vap_create(struct ieee80211com *, const char name[], int, enum ieee80211_opmode, int, const uint8_t bssid[], const uint8_t mac[]); static void rsu_vap_delete(struct ieee80211vap *); static void rsu_scan_start(struct ieee80211com *); static void rsu_scan_end(struct ieee80211com *); static void rsu_set_channel(struct ieee80211com *); static void rsu_update_mcast(struct ieee80211com *); static int rsu_alloc_rx_list(struct rsu_softc *); static void rsu_free_rx_list(struct rsu_softc *); static int rsu_alloc_tx_list(struct rsu_softc *); static void rsu_free_tx_list(struct rsu_softc *); static void rsu_free_list(struct rsu_softc *, struct rsu_data [], int); static struct rsu_data *_rsu_getbuf(struct rsu_softc *); static struct rsu_data *rsu_getbuf(struct rsu_softc *); static void rsu_freebuf(struct rsu_softc *, struct rsu_data *); static int rsu_write_region_1(struct rsu_softc *, uint16_t, uint8_t *, int); static void rsu_write_1(struct rsu_softc *, uint16_t, uint8_t); static void rsu_write_2(struct rsu_softc *, uint16_t, uint16_t); static void rsu_write_4(struct rsu_softc *, uint16_t, uint32_t); static int rsu_read_region_1(struct rsu_softc *, uint16_t, uint8_t *, int); static uint8_t rsu_read_1(struct rsu_softc *, uint16_t); static uint16_t rsu_read_2(struct rsu_softc *, uint16_t); static uint32_t rsu_read_4(struct rsu_softc *, uint16_t); static int rsu_fw_iocmd(struct rsu_softc *, uint32_t); static uint8_t rsu_efuse_read_1(struct rsu_softc *, uint16_t); static int rsu_read_rom(struct rsu_softc *); static int rsu_fw_cmd(struct rsu_softc *, uint8_t, void *, int); static void rsu_calib_task(void *, int); static void rsu_tx_task(void *, int); static int rsu_newstate(struct ieee80211vap *, enum ieee80211_state, int); #ifdef notyet static void rsu_set_key(struct rsu_softc *, const struct ieee80211_key *); static void rsu_delete_key(struct rsu_softc *, const struct ieee80211_key *); #endif static int rsu_site_survey(struct rsu_softc *, struct ieee80211vap *); static int rsu_join_bss(struct rsu_softc *, struct ieee80211_node *); static int rsu_disconnect(struct rsu_softc *); static int rsu_hwrssi_to_rssi(struct rsu_softc *, int hw_rssi); static void rsu_event_survey(struct rsu_softc *, uint8_t *, int); static void rsu_event_join_bss(struct rsu_softc *, uint8_t *, int); static void rsu_rx_event(struct rsu_softc *, uint8_t, uint8_t *, int); static void rsu_rx_multi_event(struct rsu_softc *, uint8_t *, int); #if 0 static int8_t rsu_get_rssi(struct rsu_softc *, int, void *); #endif static struct mbuf * rsu_rx_frame(struct rsu_softc *, uint8_t *, int); static struct mbuf * rsu_rx_multi_frame(struct rsu_softc *, uint8_t *, int); static struct mbuf * rsu_rxeof(struct usb_xfer *, struct rsu_data *); static void rsu_txeof(struct usb_xfer *, struct rsu_data *); static int rsu_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void rsu_init(struct rsu_softc *); static int rsu_tx_start(struct rsu_softc *, struct ieee80211_node *, struct mbuf *, struct rsu_data *); static int rsu_transmit(struct ieee80211com *, struct mbuf *); static void rsu_start(struct rsu_softc *); static void _rsu_start(struct rsu_softc *); static void rsu_parent(struct ieee80211com *); static void rsu_stop(struct rsu_softc *); static void rsu_ms_delay(struct rsu_softc *, int); static device_method_t rsu_methods[] = { DEVMETHOD(device_probe, rsu_match), DEVMETHOD(device_attach, rsu_attach), DEVMETHOD(device_detach, rsu_detach), DEVMETHOD_END }; static driver_t rsu_driver = { .name = "rsu", .methods = rsu_methods, .size = sizeof(struct rsu_softc) }; static devclass_t rsu_devclass; DRIVER_MODULE(rsu, uhub, rsu_driver, rsu_devclass, NULL, 0); MODULE_DEPEND(rsu, wlan, 1, 1, 1); MODULE_DEPEND(rsu, usb, 1, 1, 1); MODULE_DEPEND(rsu, firmware, 1, 1, 1); MODULE_VERSION(rsu, 1); USB_PNP_HOST_INFO(rsu_devs); static uint8_t rsu_wme_ac_xfer_map[4] = { [WME_AC_BE] = RSU_BULK_TX_BE_BK, [WME_AC_BK] = RSU_BULK_TX_BE_BK, [WME_AC_VI] = RSU_BULK_TX_VI_VO, [WME_AC_VO] = RSU_BULK_TX_VI_VO, }; /* XXX hard-coded */ #define RSU_H2C_ENDPOINT 3 static const struct usb_config rsu_config[RSU_N_TRANSFER] = { [RSU_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = RSU_RXBUFSZ, .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = rsu_bulk_rx_callback }, [RSU_BULK_TX_BE_BK] = { .type = UE_BULK, .endpoint = 0x06, .direction = UE_DIR_OUT, .bufsize = RSU_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .force_short_xfer = 1 }, .callback = rsu_bulk_tx_callback_be_bk, .timeout = RSU_TX_TIMEOUT }, [RSU_BULK_TX_VI_VO] = { .type = UE_BULK, .endpoint = 0x04, .direction = UE_DIR_OUT, .bufsize = RSU_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .force_short_xfer = 1 }, .callback = rsu_bulk_tx_callback_vi_vo, .timeout = RSU_TX_TIMEOUT }, [RSU_BULK_TX_H2C] = { .type = UE_BULK, .endpoint = 0x0d, .direction = UE_DIR_OUT, .bufsize = RSU_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = rsu_bulk_tx_callback_h2c, .timeout = RSU_TX_TIMEOUT }, }; static int rsu_match(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); if (uaa->usb_mode != USB_MODE_HOST || uaa->info.bIfaceIndex != 0 || uaa->info.bConfigIndex != 0) return (ENXIO); return (usbd_lookup_id_by_uaa(rsu_devs, sizeof(rsu_devs), uaa)); } static int rsu_send_mgmt(struct ieee80211_node *ni, int type, int arg) { return (ENOTSUP); } static void rsu_update_chw(struct ieee80211com *ic) { } /* * notification from net80211 that it'd like to do A-MPDU on the given TID. * * Note: this actually hangs traffic at the present moment, so don't use it. * The firmware debug does indiciate it's sending and establishing a TX AMPDU * session, but then no traffic flows. */ static int rsu_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { #if 0 struct rsu_softc *sc = ni->ni_ic->ic_softc; struct r92s_add_ba_req req; /* Don't enable if it's requested or running */ if (IEEE80211_AMPDU_REQUESTED(tap)) return (0); if (IEEE80211_AMPDU_RUNNING(tap)) return (0); /* We've decided to send addba; so send it */ req.tid = htole32(tap->txa_tid); /* Attempt net80211 state */ if (ieee80211_ampdu_tx_request_ext(ni, tap->txa_tid) != 1) return (0); /* Send the firmware command */ RSU_DPRINTF(sc, RSU_DEBUG_AMPDU, "%s: establishing AMPDU TX for TID %d\n", __func__, tap->txa_tid); RSU_LOCK(sc); if (rsu_fw_cmd(sc, R92S_CMD_ADDBA_REQ, &req, sizeof(req)) != 1) { RSU_UNLOCK(sc); /* Mark failure */ (void) ieee80211_ampdu_tx_request_active_ext(ni, tap->txa_tid, 0); return (0); } RSU_UNLOCK(sc); /* Mark success; we don't get any further notifications */ (void) ieee80211_ampdu_tx_request_active_ext(ni, tap->txa_tid, 1); #endif /* Return 0, we're driving this ourselves */ return (0); } static int rsu_wme_update(struct ieee80211com *ic) { /* Firmware handles this; not our problem */ return (0); } static int rsu_attach(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); struct rsu_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; int error; uint8_t bands[howmany(IEEE80211_MODE_MAX, 8)]; uint8_t iface_index; struct usb_interface *iface; const char *rft; device_set_usb_desc(self); sc->sc_udev = uaa->device; sc->sc_dev = self; if (rsu_enable_11n) sc->sc_ht = !! (USB_GET_DRIVER_INFO(uaa) & RSU_HT_SUPPORTED); /* Get number of endpoints */ iface = usbd_get_iface(sc->sc_udev, 0); sc->sc_nendpoints = iface->idesc->bNumEndpoints; /* Endpoints are hard-coded for now, so enforce 4-endpoint only */ if (sc->sc_nendpoints != 4) { device_printf(sc->sc_dev, "the driver currently only supports 4-endpoint devices\n"); return (ENXIO); } mtx_init(&sc->sc_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, MTX_DEF); TIMEOUT_TASK_INIT(taskqueue_thread, &sc->calib_task, 0, rsu_calib_task, sc); TASK_INIT(&sc->tx_task, 0, rsu_tx_task, sc); mbufq_init(&sc->sc_snd, ifqmaxlen); /* Allocate Tx/Rx buffers. */ error = rsu_alloc_rx_list(sc); if (error != 0) { device_printf(sc->sc_dev, "could not allocate Rx buffers\n"); goto fail_usb; } error = rsu_alloc_tx_list(sc); if (error != 0) { device_printf(sc->sc_dev, "could not allocate Tx buffers\n"); rsu_free_rx_list(sc); goto fail_usb; } iface_index = 0; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, rsu_config, RSU_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(sc->sc_dev, "could not allocate USB transfers, err=%s\n", usbd_errstr(error)); goto fail_usb; } RSU_LOCK(sc); /* Read chip revision. */ sc->cut = MS(rsu_read_4(sc, R92S_PMC_FSM), R92S_PMC_FSM_CUT); if (sc->cut != 3) sc->cut = (sc->cut >> 1) + 1; error = rsu_read_rom(sc); RSU_UNLOCK(sc); if (error != 0) { device_printf(self, "could not read ROM\n"); goto fail_rom; } /* Figure out TX/RX streams */ switch (sc->rom[84]) { case 0x0: sc->sc_rftype = RTL8712_RFCONFIG_1T1R; sc->sc_nrxstream = 1; sc->sc_ntxstream = 1; rft = "1T1R"; break; case 0x1: sc->sc_rftype = RTL8712_RFCONFIG_1T2R; sc->sc_nrxstream = 2; sc->sc_ntxstream = 1; rft = "1T2R"; break; case 0x2: sc->sc_rftype = RTL8712_RFCONFIG_2T2R; sc->sc_nrxstream = 2; sc->sc_ntxstream = 2; rft = "2T2R"; break; default: device_printf(sc->sc_dev, "%s: unknown board type (rfconfig=0x%02x)\n", __func__, sc->rom[84]); goto fail_rom; } IEEE80211_ADDR_COPY(ic->ic_macaddr, &sc->rom[0x12]); device_printf(self, "MAC/BB RTL8712 cut %d %s\n", sc->cut, rft); ic->ic_softc = sc; ic->ic_name = device_get_nameunit(self); ic->ic_phytype = IEEE80211_T_OFDM; /* Not only, but not used. */ ic->ic_opmode = IEEE80211_M_STA; /* Default to BSS mode. */ /* Set device capabilities. */ ic->ic_caps = IEEE80211_C_STA | /* station mode */ #if 0 IEEE80211_C_BGSCAN | /* Background scan. */ #endif IEEE80211_C_SHPREAMBLE | /* Short preamble supported. */ IEEE80211_C_WME | /* WME/QoS */ IEEE80211_C_SHSLOT | /* Short slot time supported. */ IEEE80211_C_WPA; /* WPA/RSN. */ /* Check if HT support is present. */ if (sc->sc_ht) { device_printf(sc->sc_dev, "%s: enabling 11n\n", __func__); /* Enable basic HT */ ic->ic_htcaps = IEEE80211_HTC_HT | +#if 0 IEEE80211_HTC_AMPDU | +#endif IEEE80211_HTC_AMSDU | IEEE80211_HTCAP_MAXAMSDU_3839 | IEEE80211_HTCAP_SMPS_OFF; ic->ic_htcaps |= IEEE80211_HTCAP_CHWIDTH40; /* set number of spatial streams */ ic->ic_txstream = sc->sc_ntxstream; ic->ic_rxstream = sc->sc_nrxstream; } /* Set supported .11b and .11g rates. */ memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); if (sc->sc_ht) setbit(bands, IEEE80211_MODE_11NG); ieee80211_init_channels(ic, NULL, bands); ieee80211_ifattach(ic); ic->ic_raw_xmit = rsu_raw_xmit; ic->ic_scan_start = rsu_scan_start; ic->ic_scan_end = rsu_scan_end; ic->ic_set_channel = rsu_set_channel; ic->ic_vap_create = rsu_vap_create; ic->ic_vap_delete = rsu_vap_delete; ic->ic_update_mcast = rsu_update_mcast; ic->ic_parent = rsu_parent; ic->ic_transmit = rsu_transmit; ic->ic_send_mgmt = rsu_send_mgmt; ic->ic_update_chw = rsu_update_chw; ic->ic_ampdu_enable = rsu_ampdu_enable; ic->ic_wme.wme_update = rsu_wme_update; ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), RSU_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), RSU_RX_RADIOTAP_PRESENT); if (bootverbose) ieee80211_announce(ic); return (0); fail_rom: usbd_transfer_unsetup(sc->sc_xfer, RSU_N_TRANSFER); fail_usb: mtx_destroy(&sc->sc_mtx); return (ENXIO); } static int rsu_detach(device_t self) { struct rsu_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; RSU_LOCK(sc); rsu_stop(sc); RSU_UNLOCK(sc); usbd_transfer_unsetup(sc->sc_xfer, RSU_N_TRANSFER); /* * Free buffers /before/ we detach from net80211, else node * references to destroyed vaps will lead to a panic. */ /* Free Tx/Rx buffers. */ RSU_LOCK(sc); rsu_free_tx_list(sc); rsu_free_rx_list(sc); RSU_UNLOCK(sc); /* Frames are freed; detach from net80211 */ ieee80211_ifdetach(ic); taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task); taskqueue_drain(taskqueue_thread, &sc->tx_task); mtx_destroy(&sc->sc_mtx); return (0); } static usb_error_t rsu_do_request(struct rsu_softc *sc, struct usb_device_request *req, void *data) { usb_error_t err; int ntries = 10; RSU_ASSERT_LOCKED(sc); while (ntries--) { err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 250 /* ms */); if (err == 0 || err == USB_ERR_NOT_CONFIGURED) break; DPRINTFN(1, "Control request failed, %s (retrying)\n", usbd_errstr(err)); rsu_ms_delay(sc, 10); } return (err); } static struct ieee80211vap * rsu_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 rsu_vap *uvp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return (NULL); uvp = malloc(sizeof(struct rsu_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &uvp->vap; if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid) != 0) { /* out of memory */ free(uvp, M_80211_VAP); return (NULL); } /* override state transition machine */ uvp->newstate = vap->iv_newstate; vap->iv_newstate = rsu_newstate; /* Limits from the r92su driver */ vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_16; vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_32K; /* complete setup */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return (vap); } static void rsu_vap_delete(struct ieee80211vap *vap) { struct rsu_vap *uvp = RSU_VAP(vap); ieee80211_vap_detach(vap); free(uvp, M_80211_VAP); } static void rsu_scan_start(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; int error; /* Scanning is done by the firmware. */ RSU_LOCK(sc); /* XXX TODO: force awake if in in network-sleep? */ error = rsu_site_survey(sc, TAILQ_FIRST(&ic->ic_vaps)); RSU_UNLOCK(sc); if (error != 0) device_printf(sc->sc_dev, "could not send site survey command\n"); } static void rsu_scan_end(struct ieee80211com *ic) { /* Nothing to do here. */ } static void rsu_set_channel(struct ieee80211com *ic __unused) { /* We are unable to switch channels, yet. */ } static void rsu_update_mcast(struct ieee80211com *ic) { /* XXX do nothing? */ } static int rsu_alloc_list(struct rsu_softc *sc, struct rsu_data data[], int ndata, int maxsz) { int i, error; for (i = 0; i < ndata; i++) { struct rsu_data *dp = &data[i]; dp->sc = sc; dp->m = NULL; dp->buf = malloc(maxsz, M_USBDEV, M_NOWAIT); if (dp->buf == NULL) { device_printf(sc->sc_dev, "could not allocate buffer\n"); error = ENOMEM; goto fail; } dp->ni = NULL; } return (0); fail: rsu_free_list(sc, data, ndata); return (error); } static int rsu_alloc_rx_list(struct rsu_softc *sc) { int error, i; error = rsu_alloc_list(sc, sc->sc_rx, RSU_RX_LIST_COUNT, RSU_RXBUFSZ); if (error != 0) return (error); STAILQ_INIT(&sc->sc_rx_active); STAILQ_INIT(&sc->sc_rx_inactive); for (i = 0; i < RSU_RX_LIST_COUNT; i++) STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], next); return (0); } static int rsu_alloc_tx_list(struct rsu_softc *sc) { int error, i; error = rsu_alloc_list(sc, sc->sc_tx, RSU_TX_LIST_COUNT, RSU_TXBUFSZ); if (error != 0) return (error); STAILQ_INIT(&sc->sc_tx_inactive); for (i = 0; i != RSU_N_TRANSFER; i++) { STAILQ_INIT(&sc->sc_tx_active[i]); STAILQ_INIT(&sc->sc_tx_pending[i]); } for (i = 0; i < RSU_TX_LIST_COUNT; i++) { STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i], next); } return (0); } static void rsu_free_tx_list(struct rsu_softc *sc) { int i; /* prevent further allocations from TX list(s) */ STAILQ_INIT(&sc->sc_tx_inactive); for (i = 0; i != RSU_N_TRANSFER; i++) { STAILQ_INIT(&sc->sc_tx_active[i]); STAILQ_INIT(&sc->sc_tx_pending[i]); } rsu_free_list(sc, sc->sc_tx, RSU_TX_LIST_COUNT); } static void rsu_free_rx_list(struct rsu_softc *sc) { /* prevent further allocations from RX list(s) */ STAILQ_INIT(&sc->sc_rx_inactive); STAILQ_INIT(&sc->sc_rx_active); rsu_free_list(sc, sc->sc_rx, RSU_RX_LIST_COUNT); } static void rsu_free_list(struct rsu_softc *sc, struct rsu_data data[], int ndata) { int i; for (i = 0; i < ndata; i++) { struct rsu_data *dp = &data[i]; if (dp->buf != NULL) { free(dp->buf, M_USBDEV); dp->buf = NULL; } if (dp->ni != NULL) { ieee80211_free_node(dp->ni); dp->ni = NULL; } } } static struct rsu_data * _rsu_getbuf(struct rsu_softc *sc) { struct rsu_data *bf; bf = STAILQ_FIRST(&sc->sc_tx_inactive); if (bf != NULL) STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next); else bf = NULL; return (bf); } static struct rsu_data * rsu_getbuf(struct rsu_softc *sc) { struct rsu_data *bf; RSU_ASSERT_LOCKED(sc); bf = _rsu_getbuf(sc); if (bf == NULL) { RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: no buffers\n", __func__); } return (bf); } static void rsu_freebuf(struct rsu_softc *sc, struct rsu_data *bf) { RSU_ASSERT_LOCKED(sc); STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, bf, next); } static int rsu_write_region_1(struct rsu_softc *sc, uint16_t addr, uint8_t *buf, int len) { usb_device_request_t req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = R92S_REQ_REGS; USETW(req.wValue, addr); USETW(req.wIndex, 0); USETW(req.wLength, len); return (rsu_do_request(sc, &req, buf)); } static void rsu_write_1(struct rsu_softc *sc, uint16_t addr, uint8_t val) { rsu_write_region_1(sc, addr, &val, 1); } static void rsu_write_2(struct rsu_softc *sc, uint16_t addr, uint16_t val) { val = htole16(val); rsu_write_region_1(sc, addr, (uint8_t *)&val, 2); } static void rsu_write_4(struct rsu_softc *sc, uint16_t addr, uint32_t val) { val = htole32(val); rsu_write_region_1(sc, addr, (uint8_t *)&val, 4); } static int rsu_read_region_1(struct rsu_softc *sc, uint16_t addr, uint8_t *buf, int len) { usb_device_request_t req; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = R92S_REQ_REGS; USETW(req.wValue, addr); USETW(req.wIndex, 0); USETW(req.wLength, len); return (rsu_do_request(sc, &req, buf)); } static uint8_t rsu_read_1(struct rsu_softc *sc, uint16_t addr) { uint8_t val; if (rsu_read_region_1(sc, addr, &val, 1) != 0) return (0xff); return (val); } static uint16_t rsu_read_2(struct rsu_softc *sc, uint16_t addr) { uint16_t val; if (rsu_read_region_1(sc, addr, (uint8_t *)&val, 2) != 0) return (0xffff); return (le16toh(val)); } static uint32_t rsu_read_4(struct rsu_softc *sc, uint16_t addr) { uint32_t val; if (rsu_read_region_1(sc, addr, (uint8_t *)&val, 4) != 0) return (0xffffffff); return (le32toh(val)); } static int rsu_fw_iocmd(struct rsu_softc *sc, uint32_t iocmd) { int ntries; rsu_write_4(sc, R92S_IOCMD_CTRL, iocmd); rsu_ms_delay(sc, 1); for (ntries = 0; ntries < 50; ntries++) { if (rsu_read_4(sc, R92S_IOCMD_CTRL) == 0) return (0); rsu_ms_delay(sc, 1); } return (ETIMEDOUT); } static uint8_t rsu_efuse_read_1(struct rsu_softc *sc, uint16_t addr) { uint32_t reg; int ntries; reg = rsu_read_4(sc, R92S_EFUSE_CTRL); reg = RW(reg, R92S_EFUSE_CTRL_ADDR, addr); reg &= ~R92S_EFUSE_CTRL_VALID; rsu_write_4(sc, R92S_EFUSE_CTRL, reg); /* Wait for read operation to complete. */ for (ntries = 0; ntries < 100; ntries++) { reg = rsu_read_4(sc, R92S_EFUSE_CTRL); if (reg & R92S_EFUSE_CTRL_VALID) return (MS(reg, R92S_EFUSE_CTRL_DATA)); rsu_ms_delay(sc, 1); } device_printf(sc->sc_dev, "could not read efuse byte at address 0x%x\n", addr); return (0xff); } static int rsu_read_rom(struct rsu_softc *sc) { uint8_t *rom = sc->rom; uint16_t addr = 0; uint32_t reg; uint8_t off, msk; int i; /* Make sure that ROM type is eFuse and that autoload succeeded. */ reg = rsu_read_1(sc, R92S_EE_9346CR); if ((reg & (R92S_9356SEL | R92S_EEPROM_EN)) != R92S_EEPROM_EN) return (EIO); /* Turn on 2.5V to prevent eFuse leakage. */ reg = rsu_read_1(sc, R92S_EFUSE_TEST + 3); rsu_write_1(sc, R92S_EFUSE_TEST + 3, reg | 0x80); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_EFUSE_TEST + 3, reg & ~0x80); /* Read full ROM image. */ memset(&sc->rom, 0xff, sizeof(sc->rom)); while (addr < 512) { reg = rsu_efuse_read_1(sc, addr); if (reg == 0xff) break; addr++; off = reg >> 4; msk = reg & 0xf; for (i = 0; i < 4; i++) { if (msk & (1 << i)) continue; rom[off * 8 + i * 2 + 0] = rsu_efuse_read_1(sc, addr); addr++; rom[off * 8 + i * 2 + 1] = rsu_efuse_read_1(sc, addr); addr++; } } #ifdef USB_DEBUG if (rsu_debug >= 5) { /* Dump ROM content. */ printf("\n"); for (i = 0; i < sizeof(sc->rom); i++) printf("%02x:", rom[i]); printf("\n"); } #endif return (0); } static int rsu_fw_cmd(struct rsu_softc *sc, uint8_t code, void *buf, int len) { const uint8_t which = RSU_H2C_ENDPOINT; struct rsu_data *data; struct r92s_tx_desc *txd; struct r92s_fw_cmd_hdr *cmd; int cmdsz; int xferlen; RSU_ASSERT_LOCKED(sc); data = rsu_getbuf(sc); if (data == NULL) return (ENOMEM); /* Blank the entire payload, just to be safe */ memset(data->buf, '\0', RSU_TXBUFSZ); /* Round-up command length to a multiple of 8 bytes. */ /* XXX TODO: is this required? */ cmdsz = (len + 7) & ~7; xferlen = sizeof(*txd) + sizeof(*cmd) + cmdsz; KASSERT(xferlen <= RSU_TXBUFSZ, ("%s: invalid length", __func__)); memset(data->buf, 0, xferlen); /* Setup Tx descriptor. */ txd = (struct r92s_tx_desc *)data->buf; txd->txdw0 = htole32( SM(R92S_TXDW0_OFFSET, sizeof(*txd)) | SM(R92S_TXDW0_PKTLEN, sizeof(*cmd) + cmdsz) | R92S_TXDW0_OWN | R92S_TXDW0_FSG | R92S_TXDW0_LSG); txd->txdw1 = htole32(SM(R92S_TXDW1_QSEL, R92S_TXDW1_QSEL_H2C)); /* Setup command header. */ cmd = (struct r92s_fw_cmd_hdr *)&txd[1]; cmd->len = htole16(cmdsz); cmd->code = code; cmd->seq = sc->cmd_seq; sc->cmd_seq = (sc->cmd_seq + 1) & 0x7f; /* Copy command payload. */ memcpy(&cmd[1], buf, len); RSU_DPRINTF(sc, RSU_DEBUG_TX | RSU_DEBUG_FWCMD, "%s: Tx cmd code=0x%x len=0x%x\n", __func__, code, cmdsz); data->buflen = xferlen; STAILQ_INSERT_TAIL(&sc->sc_tx_pending[which], data, next); usbd_transfer_start(sc->sc_xfer[which]); return (0); } /* ARGSUSED */ static void rsu_calib_task(void *arg, int pending __unused) { struct rsu_softc *sc = arg; #ifdef notyet uint32_t reg; #endif RSU_DPRINTF(sc, RSU_DEBUG_CALIB, "%s: running calibration task\n", __func__); RSU_LOCK(sc); #ifdef notyet /* Read WPS PBC status. */ rsu_write_1(sc, R92S_MAC_PINMUX_CTRL, R92S_GPIOMUX_EN | SM(R92S_GPIOSEL_GPIO, R92S_GPIOSEL_GPIO_JTAG)); rsu_write_1(sc, R92S_GPIO_IO_SEL, rsu_read_1(sc, R92S_GPIO_IO_SEL) & ~R92S_GPIO_WPS); reg = rsu_read_1(sc, R92S_GPIO_CTRL); if (reg != 0xff && (reg & R92S_GPIO_WPS)) DPRINTF(("WPS PBC is pushed\n")); #endif /* Read current signal level. */ if (rsu_fw_iocmd(sc, 0xf4000001) == 0) { sc->sc_currssi = rsu_read_4(sc, R92S_IOCMD_DATA); RSU_DPRINTF(sc, RSU_DEBUG_CALIB, "%s: RSSI=%d (%d)\n", __func__, sc->sc_currssi, rsu_hwrssi_to_rssi(sc, sc->sc_currssi)); } if (sc->sc_calibrating) taskqueue_enqueue_timeout(taskqueue_thread, &sc->calib_task, hz); RSU_UNLOCK(sc); } static void rsu_tx_task(void *arg, int pending __unused) { struct rsu_softc *sc = arg; RSU_LOCK(sc); _rsu_start(sc); RSU_UNLOCK(sc); } #define RSU_PWR_UNKNOWN 0x0 #define RSU_PWR_ACTIVE 0x1 #define RSU_PWR_OFF 0x2 #define RSU_PWR_SLEEP 0x3 /* * Set the current power state. * * The rtlwifi code doesn't do this so aggressively; it * waits for an idle period after association with * no traffic before doing this. * * For now - it's on in all states except RUN, and * in RUN it'll transition to allow sleep. */ struct r92s_pwr_cmd { uint8_t mode; uint8_t smart_ps; uint8_t bcn_pass_time; }; static int rsu_set_fw_power_state(struct rsu_softc *sc, int state) { struct r92s_set_pwr_mode cmd; //struct r92s_pwr_cmd cmd; int error; RSU_ASSERT_LOCKED(sc); /* only change state if required */ if (sc->sc_curpwrstate == state) return (0); memset(&cmd, 0, sizeof(cmd)); switch (state) { case RSU_PWR_ACTIVE: /* Force the hardware awake */ rsu_write_1(sc, R92S_USB_HRPWM, R92S_USB_HRPWM_PS_ST_ACTIVE | R92S_USB_HRPWM_PS_ALL_ON); cmd.mode = R92S_PS_MODE_ACTIVE; break; case RSU_PWR_SLEEP: cmd.mode = R92S_PS_MODE_DTIM; /* XXX configurable? */ cmd.smart_ps = 1; /* XXX 2 if doing p2p */ cmd.bcn_pass_time = 5; /* in 100mS usb.c, linux/rtlwifi */ break; case RSU_PWR_OFF: cmd.mode = R92S_PS_MODE_RADIOOFF; break; default: device_printf(sc->sc_dev, "%s: unknown ps mode (%d)\n", __func__, state); return (ENXIO); } RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting ps mode to %d (mode %d)\n", __func__, state, cmd.mode); error = rsu_fw_cmd(sc, R92S_CMD_SET_PWR_MODE, &cmd, sizeof(cmd)); if (error == 0) sc->sc_curpwrstate = state; return (error); } static int rsu_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct rsu_vap *uvp = RSU_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct rsu_softc *sc = ic->ic_softc; struct ieee80211_node *ni; struct ieee80211_rateset *rs; enum ieee80211_state ostate; int error, startcal = 0; ostate = vap->iv_state; RSU_DPRINTF(sc, RSU_DEBUG_STATE, "%s: %s -> %s\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); IEEE80211_UNLOCK(ic); if (ostate == IEEE80211_S_RUN) { RSU_LOCK(sc); /* Stop calibration. */ sc->sc_calibrating = 0; RSU_UNLOCK(sc); taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task); taskqueue_drain(taskqueue_thread, &sc->tx_task); /* Disassociate from our current BSS. */ RSU_LOCK(sc); rsu_disconnect(sc); } else RSU_LOCK(sc); switch (nstate) { case IEEE80211_S_INIT: (void) rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); break; case IEEE80211_S_AUTH: ni = ieee80211_ref_node(vap->iv_bss); (void) rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); error = rsu_join_bss(sc, ni); ieee80211_free_node(ni); if (error != 0) { device_printf(sc->sc_dev, "could not send join command\n"); } break; case IEEE80211_S_RUN: ni = ieee80211_ref_node(vap->iv_bss); rs = &ni->ni_rates; /* Indicate highest supported rate. */ ni->ni_txrate = rs->rs_rates[rs->rs_nrates - 1]; (void) rsu_set_fw_power_state(sc, RSU_PWR_SLEEP); ieee80211_free_node(ni); startcal = 1; break; default: break; } sc->sc_calibrating = 1; /* Start periodic calibration. */ taskqueue_enqueue_timeout(taskqueue_thread, &sc->calib_task, hz); RSU_UNLOCK(sc); IEEE80211_LOCK(ic); return (uvp->newstate(vap, nstate, arg)); } #ifdef notyet static void rsu_set_key(struct rsu_softc *sc, const struct ieee80211_key *k) { struct r92s_fw_cmd_set_key key; memset(&key, 0, sizeof(key)); /* Map net80211 cipher to HW crypto algorithm. */ switch (k->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_WEP: if (k->wk_keylen < 8) key.algo = R92S_KEY_ALGO_WEP40; else key.algo = R92S_KEY_ALGO_WEP104; break; case IEEE80211_CIPHER_TKIP: key.algo = R92S_KEY_ALGO_TKIP; break; case IEEE80211_CIPHER_AES_CCM: key.algo = R92S_KEY_ALGO_AES; break; default: return; } key.id = k->wk_keyix; key.grpkey = (k->wk_flags & IEEE80211_KEY_GROUP) != 0; memcpy(key.key, k->wk_key, MIN(k->wk_keylen, sizeof(key.key))); (void)rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key)); } static void rsu_delete_key(struct rsu_softc *sc, const struct ieee80211_key *k) { struct r92s_fw_cmd_set_key key; memset(&key, 0, sizeof(key)); key.id = k->wk_keyix; (void)rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key)); } #endif static int rsu_site_survey(struct rsu_softc *sc, struct ieee80211vap *vap) { struct r92s_fw_cmd_sitesurvey cmd; struct ieee80211com *ic = &sc->sc_ic; int r; RSU_ASSERT_LOCKED(sc); memset(&cmd, 0, sizeof(cmd)); if ((ic->ic_flags & IEEE80211_F_ASCAN) || sc->sc_scan_pass == 1) cmd.active = htole32(1); cmd.limit = htole32(48); if (sc->sc_scan_pass == 1 && vap->iv_des_nssid > 0) { /* Do a directed scan for second pass. */ cmd.ssidlen = htole32(vap->iv_des_ssid[0].len); memcpy(cmd.ssid, vap->iv_des_ssid[0].ssid, vap->iv_des_ssid[0].len); } DPRINTF("sending site survey command, pass=%d\n", sc->sc_scan_pass); r = rsu_fw_cmd(sc, R92S_CMD_SITE_SURVEY, &cmd, sizeof(cmd)); if (r == 0) { sc->sc_scanning = 1; } return (r); } static int rsu_join_bss(struct rsu_softc *sc, struct ieee80211_node *ni) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = ni->ni_vap; struct ndis_wlan_bssid_ex *bss; struct ndis_802_11_fixed_ies *fixed; struct r92s_fw_cmd_auth auth; uint8_t buf[sizeof(*bss) + 128] __aligned(4); uint8_t *frm; uint8_t opmode; int error; int cnt; char *msg = "rsujoin"; RSU_ASSERT_LOCKED(sc); /* * Until net80211 scanning doesn't automatically finish * before we tell it to, let's just wait until any pending * scan is done. * * XXX TODO: yes, this releases and re-acquires the lock. * We should re-verify the state whenever we re-attempt this! */ cnt = 0; while (sc->sc_scanning && cnt < 10) { device_printf(sc->sc_dev, "%s: still scanning! (attempt %d)\n", __func__, cnt); msleep(msg, &sc->sc_mtx, 0, msg, hz / 2); cnt++; } /* Let the FW decide the opmode based on the capinfo field. */ opmode = NDIS802_11AUTOUNKNOWN; RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting operating mode to %d\n", __func__, opmode); error = rsu_fw_cmd(sc, R92S_CMD_SET_OPMODE, &opmode, sizeof(opmode)); if (error != 0) return (error); memset(&auth, 0, sizeof(auth)); if (vap->iv_flags & IEEE80211_F_WPA) { auth.mode = R92S_AUTHMODE_WPA; auth.dot1x = (ni->ni_authmode == IEEE80211_AUTH_8021X); } else auth.mode = R92S_AUTHMODE_OPEN; RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting auth mode to %d\n", __func__, auth.mode); error = rsu_fw_cmd(sc, R92S_CMD_SET_AUTH, &auth, sizeof(auth)); if (error != 0) return (error); memset(buf, 0, sizeof(buf)); bss = (struct ndis_wlan_bssid_ex *)buf; IEEE80211_ADDR_COPY(bss->macaddr, ni->ni_bssid); bss->ssid.ssidlen = htole32(ni->ni_esslen); memcpy(bss->ssid.ssid, ni->ni_essid, ni->ni_esslen); if (vap->iv_flags & (IEEE80211_F_PRIVACY | IEEE80211_F_WPA)) bss->privacy = htole32(1); bss->rssi = htole32(ni->ni_avgrssi); if (ic->ic_curmode == IEEE80211_MODE_11B) bss->networktype = htole32(NDIS802_11DS); else bss->networktype = htole32(NDIS802_11OFDM24); bss->config.len = htole32(sizeof(bss->config)); bss->config.bintval = htole32(ni->ni_intval); bss->config.dsconfig = htole32(ieee80211_chan2ieee(ic, ni->ni_chan)); bss->inframode = htole32(NDIS802_11INFRASTRUCTURE); /* XXX verify how this is supposed to look! */ memcpy(bss->supprates, ni->ni_rates.rs_rates, ni->ni_rates.rs_nrates); /* Write the fixed fields of the beacon frame. */ fixed = (struct ndis_802_11_fixed_ies *)&bss[1]; memcpy(&fixed->tstamp, ni->ni_tstamp.data, 8); fixed->bintval = htole16(ni->ni_intval); fixed->capabilities = htole16(ni->ni_capinfo); /* Write IEs to be included in the association request. */ frm = (uint8_t *)&fixed[1]; frm = ieee80211_add_rsn(frm, vap); frm = ieee80211_add_wpa(frm, vap); frm = ieee80211_add_qos(frm, ni); if ((ic->ic_flags & IEEE80211_F_WME) && (ni->ni_ies.wme_ie != NULL)) frm = ieee80211_add_wme_info(frm, &ic->ic_wme); if (ni->ni_flags & IEEE80211_NODE_HT) { frm = ieee80211_add_htcap(frm, ni); frm = ieee80211_add_htinfo(frm, ni); } bss->ieslen = htole32(frm - (uint8_t *)fixed); bss->len = htole32(((frm - buf) + 3) & ~3); RSU_DPRINTF(sc, RSU_DEBUG_RESET | RSU_DEBUG_FWCMD, "%s: sending join bss command to %s chan %d\n", __func__, ether_sprintf(bss->macaddr), le32toh(bss->config.dsconfig)); return (rsu_fw_cmd(sc, R92S_CMD_JOIN_BSS, buf, sizeof(buf))); } static int rsu_disconnect(struct rsu_softc *sc) { uint32_t zero = 0; /* :-) */ /* Disassociate from our current BSS. */ RSU_DPRINTF(sc, RSU_DEBUG_STATE | RSU_DEBUG_FWCMD, "%s: sending disconnect command\n", __func__); return (rsu_fw_cmd(sc, R92S_CMD_DISCONNECT, &zero, sizeof(zero))); } /* * Map the hardware provided RSSI value to a signal level. * For the most part it's just something we divide by and cap * so it doesn't overflow the representation by net80211. */ static int rsu_hwrssi_to_rssi(struct rsu_softc *sc, int hw_rssi) { int v; if (hw_rssi == 0) return (0); v = hw_rssi >> 4; if (v > 80) v = 80; return (v); } static void rsu_event_survey(struct rsu_softc *sc, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame *wh; struct ndis_wlan_bssid_ex *bss; struct ieee80211_rx_stats rxs; struct mbuf *m; int pktlen; if (__predict_false(len < sizeof(*bss))) return; bss = (struct ndis_wlan_bssid_ex *)buf; if (__predict_false(len < sizeof(*bss) + le32toh(bss->ieslen))) return; RSU_DPRINTF(sc, RSU_DEBUG_SCAN, "%s: found BSS %s: len=%d chan=%d inframode=%d " "networktype=%d privacy=%d, RSSI=%d\n", __func__, ether_sprintf(bss->macaddr), le32toh(bss->len), le32toh(bss->config.dsconfig), le32toh(bss->inframode), le32toh(bss->networktype), le32toh(bss->privacy), le32toh(bss->rssi)); /* Build a fake beacon frame to let net80211 do all the parsing. */ /* XXX TODO: just call the new scan API methods! */ pktlen = sizeof(*wh) + le32toh(bss->ieslen); if (__predict_false(pktlen > MCLBYTES)) return; m = m_get2(pktlen, M_NOWAIT, MT_DATA, M_PKTHDR); if (__predict_false(m == NULL)) return; wh = mtod(m, struct ieee80211_frame *); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_BEACON; wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; USETW(wh->i_dur, 0); IEEE80211_ADDR_COPY(wh->i_addr1, ieee80211broadcastaddr); IEEE80211_ADDR_COPY(wh->i_addr2, bss->macaddr); IEEE80211_ADDR_COPY(wh->i_addr3, bss->macaddr); *(uint16_t *)wh->i_seq = 0; memcpy(&wh[1], (uint8_t *)&bss[1], le32toh(bss->ieslen)); /* Finalize mbuf. */ m->m_pkthdr.len = m->m_len = pktlen; /* Set channel flags for input path */ bzero(&rxs, sizeof(rxs)); rxs.r_flags |= IEEE80211_R_IEEE | IEEE80211_R_FREQ; rxs.r_flags |= IEEE80211_R_NF | IEEE80211_R_RSSI; rxs.c_ieee = le32toh(bss->config.dsconfig); rxs.c_freq = ieee80211_ieee2mhz(rxs.c_ieee, IEEE80211_CHAN_2GHZ); /* This is a number from 0..100; so let's just divide it down a bit */ rxs.rssi = le32toh(bss->rssi) / 2; rxs.nf = -96; /* XXX avoid a LOR */ RSU_UNLOCK(sc); ieee80211_input_mimo_all(ic, m, &rxs); RSU_LOCK(sc); } static void rsu_event_join_bss(struct rsu_softc *sc, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct ieee80211_node *ni = vap->iv_bss; struct r92s_event_join_bss *rsp; uint32_t tmp; int res; if (__predict_false(len < sizeof(*rsp))) return; rsp = (struct r92s_event_join_bss *)buf; res = (int)le32toh(rsp->join_res); RSU_DPRINTF(sc, RSU_DEBUG_STATE | RSU_DEBUG_FWCMD, "%s: Rx join BSS event len=%d res=%d\n", __func__, len, res); /* * XXX Don't do this; there's likely a better way to tell * the caller we failed. */ if (res <= 0) { RSU_UNLOCK(sc); ieee80211_new_state(vap, IEEE80211_S_SCAN, -1); RSU_LOCK(sc); return; } tmp = le32toh(rsp->associd); if (tmp >= vap->iv_max_aid) { DPRINTF("Assoc ID overflow\n"); tmp = 1; } RSU_DPRINTF(sc, RSU_DEBUG_STATE | RSU_DEBUG_FWCMD, "%s: associated with %s associd=%d\n", __func__, ether_sprintf(rsp->bss.macaddr), tmp); /* XXX is this required? What's the top two bits for again? */ ni->ni_associd = tmp | 0xc000; RSU_UNLOCK(sc); ieee80211_new_state(vap, IEEE80211_S_RUN, IEEE80211_FC0_SUBTYPE_ASSOC_RESP); RSU_LOCK(sc); } static void rsu_event_addba_req_report(struct rsu_softc *sc, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct r92s_add_ba_event *ba = (void *) buf; struct ieee80211_node *ni; if (len < sizeof(*ba)) { device_printf(sc->sc_dev, "%s: short read (%d)\n", __func__, len); return; } if (vap == NULL) return; RSU_DPRINTF(sc, RSU_DEBUG_AMPDU, "%s: mac=%s, tid=%d, ssn=%d\n", __func__, ether_sprintf(ba->mac_addr), (int) ba->tid, (int) le16toh(ba->ssn)); /* XXX do node lookup; this is STA specific */ ni = ieee80211_ref_node(vap->iv_bss); ieee80211_ampdu_rx_start_ext(ni, ba->tid, le16toh(ba->ssn) >> 4, 32); ieee80211_free_node(ni); } static void rsu_rx_event(struct rsu_softc *sc, uint8_t code, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); RSU_DPRINTF(sc, RSU_DEBUG_RX | RSU_DEBUG_FWCMD, "%s: Rx event code=%d len=%d\n", __func__, code, len); switch (code) { case R92S_EVT_SURVEY: rsu_event_survey(sc, buf, len); break; case R92S_EVT_SURVEY_DONE: RSU_DPRINTF(sc, RSU_DEBUG_SCAN, "%s: site survey pass %d done, found %d BSS\n", __func__, sc->sc_scan_pass, le32toh(*(uint32_t *)buf)); sc->sc_scanning = 0; if (vap->iv_state != IEEE80211_S_SCAN) break; /* Ignore if not scanning. */ /* * XXX TODO: This needs to be done without a transition to * the SCAN state again. Grr. */ if (sc->sc_scan_pass == 0 && vap->iv_des_nssid != 0) { /* Schedule a directed scan for hidden APs. */ /* XXX bad! */ sc->sc_scan_pass = 1; RSU_UNLOCK(sc); ieee80211_new_state(vap, IEEE80211_S_SCAN, -1); RSU_LOCK(sc); break; } sc->sc_scan_pass = 0; break; case R92S_EVT_JOIN_BSS: if (vap->iv_state == IEEE80211_S_AUTH) rsu_event_join_bss(sc, buf, len); break; case R92S_EVT_DEL_STA: RSU_DPRINTF(sc, RSU_DEBUG_FWCMD | RSU_DEBUG_STATE, "%s: disassociated from %s\n", __func__, ether_sprintf(buf)); if (vap->iv_state == IEEE80211_S_RUN && IEEE80211_ADDR_EQ(vap->iv_bss->ni_bssid, buf)) { RSU_UNLOCK(sc); ieee80211_new_state(vap, IEEE80211_S_SCAN, -1); RSU_LOCK(sc); } break; case R92S_EVT_WPS_PBC: RSU_DPRINTF(sc, RSU_DEBUG_RX | RSU_DEBUG_FWCMD, "%s: WPS PBC pushed.\n", __func__); break; case R92S_EVT_FWDBG: buf[60] = '\0'; RSU_DPRINTF(sc, RSU_DEBUG_FWDBG, "FWDBG: %s\n", (char *)buf); break; case R92S_EVT_ADDBA_REQ_REPORT: rsu_event_addba_req_report(sc, buf, len); break; default: device_printf(sc->sc_dev, "%s: unhandled code (%d)\n", __func__, code); break; } } static void rsu_rx_multi_event(struct rsu_softc *sc, uint8_t *buf, int len) { struct r92s_fw_cmd_hdr *cmd; int cmdsz; RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: Rx events len=%d\n", __func__, len); /* Skip Rx status. */ buf += sizeof(struct r92s_rx_stat); len -= sizeof(struct r92s_rx_stat); /* Process all events. */ for (;;) { /* Check that command header fits. */ if (__predict_false(len < sizeof(*cmd))) break; cmd = (struct r92s_fw_cmd_hdr *)buf; /* Check that command payload fits. */ cmdsz = le16toh(cmd->len); if (__predict_false(len < sizeof(*cmd) + cmdsz)) break; /* Process firmware event. */ rsu_rx_event(sc, cmd->code, (uint8_t *)&cmd[1], cmdsz); if (!(cmd->seq & R92S_FW_CMD_MORE)) break; buf += sizeof(*cmd) + cmdsz; len -= sizeof(*cmd) + cmdsz; } } #if 0 static int8_t rsu_get_rssi(struct rsu_softc *sc, int rate, void *physt) { static const int8_t cckoff[] = { 14, -2, -20, -40 }; struct r92s_rx_phystat *phy; struct r92s_rx_cck *cck; uint8_t rpt; int8_t rssi; if (rate <= 3) { cck = (struct r92s_rx_cck *)physt; rpt = (cck->agc_rpt >> 6) & 0x3; rssi = cck->agc_rpt & 0x3e; rssi = cckoff[rpt] - rssi; } else { /* OFDM/HT. */ phy = (struct r92s_rx_phystat *)physt; rssi = ((le32toh(phy->phydw1) >> 1) & 0x7f) - 106; } return (rssi); } #endif static struct mbuf * rsu_rx_frame(struct rsu_softc *sc, uint8_t *buf, int pktlen) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame *wh; struct r92s_rx_stat *stat; uint32_t rxdw0, rxdw3; struct mbuf *m; uint8_t rate; int infosz; stat = (struct r92s_rx_stat *)buf; rxdw0 = le32toh(stat->rxdw0); rxdw3 = le32toh(stat->rxdw3); if (__predict_false(rxdw0 & R92S_RXDW0_CRCERR)) { counter_u64_add(ic->ic_ierrors, 1); return NULL; } if (__predict_false(pktlen < sizeof(*wh) || pktlen > MCLBYTES)) { counter_u64_add(ic->ic_ierrors, 1); return NULL; } rate = MS(rxdw3, R92S_RXDW3_RATE); infosz = MS(rxdw0, R92S_RXDW0_INFOSZ) * 8; #if 0 /* Get RSSI from PHY status descriptor if present. */ if (infosz != 0) *rssi = rsu_get_rssi(sc, rate, &stat[1]); else *rssi = 0; #endif RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: Rx frame len=%d rate=%d infosz=%d\n", __func__, pktlen, rate, infosz); m = m_get2(pktlen, M_NOWAIT, MT_DATA, M_PKTHDR); if (__predict_false(m == NULL)) { counter_u64_add(ic->ic_ierrors, 1); return NULL; } /* Hardware does Rx TCP checksum offload. */ if (rxdw3 & R92S_RXDW3_TCPCHKVALID) { if (__predict_true(rxdw3 & R92S_RXDW3_TCPCHKRPT)) m->m_pkthdr.csum_flags |= CSUM_DATA_VALID; } wh = (struct ieee80211_frame *)((uint8_t *)&stat[1] + infosz); memcpy(mtod(m, uint8_t *), wh, pktlen); m->m_pkthdr.len = m->m_len = pktlen; if (ieee80211_radiotap_active(ic)) { struct rsu_rx_radiotap_header *tap = &sc->sc_rxtap; /* Map HW rate index to 802.11 rate. */ tap->wr_flags = 2; if (!(rxdw3 & R92S_RXDW3_HTC)) { switch (rate) { /* CCK. */ case 0: tap->wr_rate = 2; break; case 1: tap->wr_rate = 4; break; case 2: tap->wr_rate = 11; break; case 3: tap->wr_rate = 22; break; /* OFDM. */ case 4: tap->wr_rate = 12; break; case 5: tap->wr_rate = 18; break; case 6: tap->wr_rate = 24; break; case 7: tap->wr_rate = 36; break; case 8: tap->wr_rate = 48; break; case 9: tap->wr_rate = 72; break; case 10: tap->wr_rate = 96; break; case 11: tap->wr_rate = 108; break; } } else if (rate >= 12) { /* MCS0~15. */ /* Bit 7 set means HT MCS instead of rate. */ tap->wr_rate = 0x80 | (rate - 12); } #if 0 tap->wr_dbm_antsignal = *rssi; #endif /* XXX not nice */ tap->wr_dbm_antsignal = rsu_hwrssi_to_rssi(sc, sc->sc_currssi); tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq); tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); } return (m); } static struct mbuf * rsu_rx_multi_frame(struct rsu_softc *sc, uint8_t *buf, int len) { struct r92s_rx_stat *stat; uint32_t rxdw0; int totlen, pktlen, infosz, npkts; struct mbuf *m, *m0 = NULL, *prevm = NULL; /* Get the number of encapsulated frames. */ stat = (struct r92s_rx_stat *)buf; npkts = MS(le32toh(stat->rxdw2), R92S_RXDW2_PKTCNT); RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: Rx %d frames in one chunk\n", __func__, npkts); /* Process all of them. */ while (npkts-- > 0) { if (__predict_false(len < sizeof(*stat))) break; stat = (struct r92s_rx_stat *)buf; rxdw0 = le32toh(stat->rxdw0); pktlen = MS(rxdw0, R92S_RXDW0_PKTLEN); if (__predict_false(pktlen == 0)) break; infosz = MS(rxdw0, R92S_RXDW0_INFOSZ) * 8; /* Make sure everything fits in xfer. */ totlen = sizeof(*stat) + infosz + pktlen; if (__predict_false(totlen > len)) break; /* Process 802.11 frame. */ m = rsu_rx_frame(sc, buf, pktlen); if (m0 == NULL) m0 = m; if (prevm == NULL) prevm = m; else { prevm->m_next = m; prevm = m; } /* Next chunk is 128-byte aligned. */ totlen = (totlen + 127) & ~127; buf += totlen; len -= totlen; } return (m0); } static struct mbuf * rsu_rxeof(struct usb_xfer *xfer, struct rsu_data *data) { struct rsu_softc *sc = data->sc; struct ieee80211com *ic = &sc->sc_ic; struct r92s_rx_stat *stat; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); if (__predict_false(len < sizeof(*stat))) { DPRINTF("xfer too short %d\n", len); counter_u64_add(ic->ic_ierrors, 1); return (NULL); } /* Determine if it is a firmware C2H event or an 802.11 frame. */ stat = (struct r92s_rx_stat *)data->buf; if ((le32toh(stat->rxdw1) & 0x1ff) == 0x1ff) { rsu_rx_multi_event(sc, data->buf, len); /* No packets to process. */ return (NULL); } else return (rsu_rx_multi_frame(sc, data->buf, len)); } static void rsu_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct rsu_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct mbuf *m = NULL, *next; struct rsu_data *data; RSU_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_rx_active); if (data == NULL) goto tr_setup; STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); m = rsu_rxeof(xfer, data); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: /* * XXX TODO: if we have an mbuf list, but then * we hit data == NULL, what now? */ data = STAILQ_FIRST(&sc->sc_rx_inactive); if (data == NULL) { KASSERT(m == NULL, ("mbuf isn't NULL")); return; } STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next); STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); /* * To avoid LOR we should unlock our private mutex here to call * ieee80211_input() because here is at the end of a USB * callback and safe to unlock. */ RSU_UNLOCK(sc); while (m != NULL) { int rssi; /* Cheat and get the last calibrated RSSI */ rssi = rsu_hwrssi_to_rssi(sc, sc->sc_currssi); next = m->m_next; m->m_next = NULL; wh = mtod(m, struct ieee80211_frame *); ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh); if (ni != NULL) { if (ni->ni_flags & IEEE80211_NODE_HT) m->m_flags |= M_AMPDU; (void)ieee80211_input(ni, m, rssi, -96); ieee80211_free_node(ni); } else (void)ieee80211_input_all(ic, m, rssi, -96); m = next; } RSU_LOCK(sc); break; default: /* needs it to the inactive queue due to a error. */ data = STAILQ_FIRST(&sc->sc_rx_active); if (data != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); } if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } break; } } static void rsu_txeof(struct usb_xfer *xfer, struct rsu_data *data) { #ifdef USB_DEBUG struct rsu_softc *sc = usbd_xfer_softc(xfer); #endif RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: called; data=%p\n", __func__, data); if (data->m) { /* XXX status? */ ieee80211_tx_complete(data->ni, data->m, 0); data->m = NULL; data->ni = NULL; } } static void rsu_bulk_tx_callback_sub(struct usb_xfer *xfer, usb_error_t error, uint8_t which) { struct rsu_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct rsu_data *data; RSU_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_tx_active[which]); if (data == NULL) goto tr_setup; RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: transfer done %p\n", __func__, data); STAILQ_REMOVE_HEAD(&sc->sc_tx_active[which], next); rsu_txeof(xfer, data); rsu_freebuf(sc, data); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: data = STAILQ_FIRST(&sc->sc_tx_pending[which]); if (data == NULL) { RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: empty pending queue sc %p\n", __func__, sc); return; } STAILQ_REMOVE_HEAD(&sc->sc_tx_pending[which], next); STAILQ_INSERT_TAIL(&sc->sc_tx_active[which], data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen); RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: submitting transfer %p\n", __func__, data); usbd_transfer_submit(xfer); break; default: data = STAILQ_FIRST(&sc->sc_tx_active[which]); if (data != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_tx_active[which], next); rsu_txeof(xfer, data); rsu_freebuf(sc, data); } counter_u64_add(ic->ic_oerrors, 1); if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } break; } /* * XXX TODO: if the queue is low, flush out FF TX frames. * Remember to unlock the driver for now; net80211 doesn't * defer it for us. */ } static void rsu_bulk_tx_callback_be_bk(struct usb_xfer *xfer, usb_error_t error) { struct rsu_softc *sc = usbd_xfer_softc(xfer); rsu_bulk_tx_callback_sub(xfer, error, RSU_BULK_TX_BE_BK); /* This kicks the TX taskqueue */ rsu_start(sc); } static void rsu_bulk_tx_callback_vi_vo(struct usb_xfer *xfer, usb_error_t error) { struct rsu_softc *sc = usbd_xfer_softc(xfer); rsu_bulk_tx_callback_sub(xfer, error, RSU_BULK_TX_VI_VO); /* This kicks the TX taskqueue */ rsu_start(sc); } static void rsu_bulk_tx_callback_h2c(struct usb_xfer *xfer, usb_error_t error) { struct rsu_softc *sc = usbd_xfer_softc(xfer); rsu_bulk_tx_callback_sub(xfer, error, RSU_BULK_TX_H2C); /* This kicks the TX taskqueue */ rsu_start(sc); } /* * Transmit the given frame. * * This doesn't free the node or mbuf upon failure. */ static int rsu_tx_start(struct rsu_softc *sc, struct ieee80211_node *ni, struct mbuf *m0, struct rsu_data *data) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_frame *wh; struct ieee80211_key *k = NULL; struct r92s_tx_desc *txd; uint8_t type; int prio = 0; uint8_t which; int hasqos; int xferlen; int qid; RSU_ASSERT_LOCKED(sc); wh = mtod(m0, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: data=%p, m=%p\n", __func__, data, m0); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m0); if (k == NULL) { device_printf(sc->sc_dev, "ieee80211_crypto_encap returns NULL.\n"); /* XXX we don't expect the fragmented frames */ return (ENOBUFS); } wh = mtod(m0, struct ieee80211_frame *); } /* If we have QoS then use it */ /* XXX TODO: mbuf WME/PRI versus TID? */ if (IEEE80211_QOS_HAS_SEQ(wh)) { /* Has QoS */ prio = M_WME_GETAC(m0); which = rsu_wme_ac_xfer_map[prio]; hasqos = 1; } else { /* Non-QoS TID */ /* XXX TODO: tid=0 for non-qos TID? */ which = rsu_wme_ac_xfer_map[WME_AC_BE]; hasqos = 0; prio = 0; } qid = rsu_ac2qid[prio]; #if 0 switch (type) { case IEEE80211_FC0_TYPE_CTL: case IEEE80211_FC0_TYPE_MGT: which = rsu_wme_ac_xfer_map[WME_AC_VO]; break; default: which = rsu_wme_ac_xfer_map[M_WME_GETAC(m0)]; break; } hasqos = 0; #endif RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: pri=%d, which=%d, hasqos=%d\n", __func__, prio, which, hasqos); /* Fill Tx descriptor. */ txd = (struct r92s_tx_desc *)data->buf; memset(txd, 0, sizeof(*txd)); txd->txdw0 |= htole32( SM(R92S_TXDW0_PKTLEN, m0->m_pkthdr.len) | SM(R92S_TXDW0_OFFSET, sizeof(*txd)) | R92S_TXDW0_OWN | R92S_TXDW0_FSG | R92S_TXDW0_LSG); txd->txdw1 |= htole32( SM(R92S_TXDW1_MACID, R92S_MACID_BSS) | SM(R92S_TXDW1_QSEL, qid)); if (!hasqos) txd->txdw1 |= htole32(R92S_TXDW1_NONQOS); #ifdef notyet if (k != NULL) { switch (k->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_WEP: cipher = R92S_TXDW1_CIPHER_WEP; break; case IEEE80211_CIPHER_TKIP: cipher = R92S_TXDW1_CIPHER_TKIP; break; case IEEE80211_CIPHER_AES_CCM: cipher = R92S_TXDW1_CIPHER_AES; break; default: cipher = R92S_TXDW1_CIPHER_NONE; } txd->txdw1 |= htole32( SM(R92S_TXDW1_CIPHER, cipher) | SM(R92S_TXDW1_KEYIDX, k->k_id)); } #endif /* XXX todo: set AGGEN bit if appropriate? */ txd->txdw2 |= htole32(R92S_TXDW2_BK); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) txd->txdw2 |= htole32(R92S_TXDW2_BMCAST); /* * Firmware will use and increment the sequence number for the * specified priority. */ txd->txdw3 |= htole32(SM(R92S_TXDW3_SEQ, prio)); if (ieee80211_radiotap_active_vap(vap)) { struct rsu_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); ieee80211_radiotap_tx(vap, m0); } xferlen = sizeof(*txd) + m0->m_pkthdr.len; m_copydata(m0, 0, m0->m_pkthdr.len, (caddr_t)&txd[1]); data->buflen = xferlen; data->ni = ni; data->m = m0; STAILQ_INSERT_TAIL(&sc->sc_tx_pending[which], data, next); /* start transfer, if any */ usbd_transfer_start(sc->sc_xfer[which]); return (0); } static int rsu_transmit(struct ieee80211com *ic, struct mbuf *m) { struct rsu_softc *sc = ic->ic_softc; int error; RSU_LOCK(sc); if (!sc->sc_running) { RSU_UNLOCK(sc); return (ENXIO); } /* * XXX TODO: ensure that we treat 'm' as a list of frames * to transmit! */ error = mbufq_enqueue(&sc->sc_snd, m); if (error) { RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: mbufq_enable: failed (%d)\n", __func__, error); RSU_UNLOCK(sc); return (error); } RSU_UNLOCK(sc); /* This kicks the TX taskqueue */ rsu_start(sc); return (0); } static void rsu_drain_mbufq(struct rsu_softc *sc) { struct mbuf *m; struct ieee80211_node *ni; RSU_ASSERT_LOCKED(sc); while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; ieee80211_free_node(ni); m_freem(m); } } static void _rsu_start(struct rsu_softc *sc) { struct ieee80211_node *ni; struct rsu_data *bf; struct mbuf *m; RSU_ASSERT_LOCKED(sc); while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { bf = rsu_getbuf(sc); if (bf == NULL) { RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: failed to get buffer\n", __func__); mbufq_prepend(&sc->sc_snd, m); break; } ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; if (rsu_tx_start(sc, ni, m, bf) != 0) { RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: failed to transmit\n", __func__); if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); rsu_freebuf(sc, bf); ieee80211_free_node(ni); m_freem(m); break; } } } static void rsu_start(struct rsu_softc *sc) { taskqueue_enqueue(taskqueue_thread, &sc->tx_task); } static void rsu_parent(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; int startall = 0; RSU_LOCK(sc); if (ic->ic_nrunning > 0) { if (!sc->sc_running) { rsu_init(sc); startall = 1; } } else if (sc->sc_running) rsu_stop(sc); RSU_UNLOCK(sc); if (startall) ieee80211_start_all(ic); } /* * Power on sequence for A-cut adapters. */ static void rsu_power_on_acut(struct rsu_softc *sc) { uint32_t reg; rsu_write_1(sc, R92S_SPS0_CTRL + 1, 0x53); rsu_write_1(sc, R92S_SPS0_CTRL + 0, 0x57); /* Enable AFE macro block's bandgap and Mbias. */ rsu_write_1(sc, R92S_AFE_MISC, rsu_read_1(sc, R92S_AFE_MISC) | R92S_AFE_MISC_BGEN | R92S_AFE_MISC_MBEN); /* Enable LDOA15 block. */ rsu_write_1(sc, R92S_LDOA15_CTRL, rsu_read_1(sc, R92S_LDOA15_CTRL) | R92S_LDA15_EN); rsu_write_1(sc, R92S_SPS1_CTRL, rsu_read_1(sc, R92S_SPS1_CTRL) | R92S_SPS1_LDEN); rsu_ms_delay(sc, 2000); /* Enable switch regulator block. */ rsu_write_1(sc, R92S_SPS1_CTRL, rsu_read_1(sc, R92S_SPS1_CTRL) | R92S_SPS1_SWEN); rsu_write_4(sc, R92S_SPS1_CTRL, 0x00a7b267); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) | 0x08); rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x20); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) & ~0x90); /* Enable AFE clock. */ rsu_write_1(sc, R92S_AFE_XTAL_CTRL + 1, rsu_read_1(sc, R92S_AFE_XTAL_CTRL + 1) & ~0x04); /* Enable AFE PLL macro block. */ rsu_write_1(sc, R92S_AFE_PLL_CTRL, rsu_read_1(sc, R92S_AFE_PLL_CTRL) | 0x11); /* Attach AFE PLL to MACTOP/BB. */ rsu_write_1(sc, R92S_SYS_ISO_CTRL, rsu_read_1(sc, R92S_SYS_ISO_CTRL) & ~0x11); /* Switch to 40MHz clock instead of 80MHz. */ rsu_write_2(sc, R92S_SYS_CLKR, rsu_read_2(sc, R92S_SYS_CLKR) & ~R92S_SYS_CLKSEL); /* Enable MAC clock. */ rsu_write_2(sc, R92S_SYS_CLKR, rsu_read_2(sc, R92S_SYS_CLKR) | R92S_MAC_CLK_EN | R92S_SYS_CLK_EN); rsu_write_1(sc, R92S_PMC_FSM, 0x02); /* Enable digital core and IOREG R/W. */ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x08); rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x80); /* Switch the control path to firmware. */ reg = rsu_read_2(sc, R92S_SYS_CLKR); reg = (reg & ~R92S_SWHW_SEL) | R92S_FWHW_SEL; rsu_write_2(sc, R92S_SYS_CLKR, reg); rsu_write_2(sc, R92S_CR, 0x37fc); /* Fix USB RX FIFO issue. */ rsu_write_1(sc, 0xfe5c, rsu_read_1(sc, 0xfe5c) | 0x80); rsu_write_1(sc, 0x00ab, rsu_read_1(sc, 0x00ab) | 0xc0); rsu_write_1(sc, R92S_SYS_CLKR, rsu_read_1(sc, R92S_SYS_CLKR) & ~R92S_SYS_CPU_CLKSEL); } /* * Power on sequence for B-cut and C-cut adapters. */ static void rsu_power_on_bcut(struct rsu_softc *sc) { uint32_t reg; int ntries; /* Prevent eFuse leakage. */ rsu_write_1(sc, 0x37, 0xb0); rsu_ms_delay(sc, 10); rsu_write_1(sc, 0x37, 0x30); /* Switch the control path to hardware. */ reg = rsu_read_2(sc, R92S_SYS_CLKR); if (reg & R92S_FWHW_SEL) { rsu_write_2(sc, R92S_SYS_CLKR, reg & ~(R92S_SWHW_SEL | R92S_FWHW_SEL)); } rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) & ~0x8c); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_SPS0_CTRL + 1, 0x53); rsu_write_1(sc, R92S_SPS0_CTRL + 0, 0x57); reg = rsu_read_1(sc, R92S_AFE_MISC); rsu_write_1(sc, R92S_AFE_MISC, reg | R92S_AFE_MISC_BGEN); rsu_write_1(sc, R92S_AFE_MISC, reg | R92S_AFE_MISC_BGEN | R92S_AFE_MISC_MBEN | R92S_AFE_MISC_I32_EN); /* Enable PLL. */ rsu_write_1(sc, R92S_LDOA15_CTRL, rsu_read_1(sc, R92S_LDOA15_CTRL) | R92S_LDA15_EN); rsu_write_1(sc, R92S_LDOV12D_CTRL, rsu_read_1(sc, R92S_LDOV12D_CTRL) | R92S_LDV12_EN); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) | 0x08); rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x20); /* Support 64KB IMEM. */ rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) & ~0x97); /* Enable AFE clock. */ rsu_write_1(sc, R92S_AFE_XTAL_CTRL + 1, rsu_read_1(sc, R92S_AFE_XTAL_CTRL + 1) & ~0x04); /* Enable AFE PLL macro block. */ reg = rsu_read_1(sc, R92S_AFE_PLL_CTRL); rsu_write_1(sc, R92S_AFE_PLL_CTRL, reg | 0x11); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_AFE_PLL_CTRL, reg | 0x51); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_AFE_PLL_CTRL, reg | 0x11); rsu_ms_delay(sc, 1); /* Attach AFE PLL to MACTOP/BB. */ rsu_write_1(sc, R92S_SYS_ISO_CTRL, rsu_read_1(sc, R92S_SYS_ISO_CTRL) & ~0x11); /* Switch to 40MHz clock. */ rsu_write_1(sc, R92S_SYS_CLKR, 0x00); /* Disable CPU clock and 80MHz SSC. */ rsu_write_1(sc, R92S_SYS_CLKR, rsu_read_1(sc, R92S_SYS_CLKR) | 0xa0); /* Enable MAC clock. */ rsu_write_2(sc, R92S_SYS_CLKR, rsu_read_2(sc, R92S_SYS_CLKR) | R92S_MAC_CLK_EN | R92S_SYS_CLK_EN); rsu_write_1(sc, R92S_PMC_FSM, 0x02); /* Enable digital core and IOREG R/W. */ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x08); rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x80); /* Switch the control path to firmware. */ reg = rsu_read_2(sc, R92S_SYS_CLKR); reg = (reg & ~R92S_SWHW_SEL) | R92S_FWHW_SEL; rsu_write_2(sc, R92S_SYS_CLKR, reg); rsu_write_2(sc, R92S_CR, 0x37fc); /* Fix USB RX FIFO issue. */ rsu_write_1(sc, 0xfe5c, rsu_read_1(sc, 0xfe5c) | 0x80); rsu_write_1(sc, R92S_SYS_CLKR, rsu_read_1(sc, R92S_SYS_CLKR) & ~R92S_SYS_CPU_CLKSEL); rsu_write_1(sc, 0xfe1c, 0x80); /* Make sure TxDMA is ready to download firmware. */ for (ntries = 0; ntries < 20; ntries++) { reg = rsu_read_1(sc, R92S_TCR); if ((reg & (R92S_TCR_IMEM_CHK_RPT | R92S_TCR_EMEM_CHK_RPT)) == (R92S_TCR_IMEM_CHK_RPT | R92S_TCR_EMEM_CHK_RPT)) break; rsu_ms_delay(sc, 1); } if (ntries == 20) { RSU_DPRINTF(sc, RSU_DEBUG_RESET | RSU_DEBUG_TX, "%s: TxDMA is not ready\n", __func__); /* Reset TxDMA. */ reg = rsu_read_1(sc, R92S_CR); rsu_write_1(sc, R92S_CR, reg & ~R92S_CR_TXDMA_EN); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_CR, reg | R92S_CR_TXDMA_EN); } } static void rsu_power_off(struct rsu_softc *sc) { /* Turn RF off. */ rsu_write_1(sc, R92S_RF_CTRL, 0x00); rsu_ms_delay(sc, 5); /* Turn MAC off. */ /* Switch control path. */ rsu_write_1(sc, R92S_SYS_CLKR + 1, 0x38); /* Reset MACTOP. */ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, 0x70); rsu_write_1(sc, R92S_PMC_FSM, 0x06); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 0, 0xf9); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, 0xe8); /* Disable AFE PLL. */ rsu_write_1(sc, R92S_AFE_PLL_CTRL, 0x00); /* Disable A15V. */ rsu_write_1(sc, R92S_LDOA15_CTRL, 0x54); /* Disable eFuse 1.2V. */ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, 0x50); rsu_write_1(sc, R92S_LDOV12D_CTRL, 0x24); /* Enable AFE macro block's bandgap and Mbias. */ rsu_write_1(sc, R92S_AFE_MISC, 0x30); /* Disable 1.6V LDO. */ rsu_write_1(sc, R92S_SPS0_CTRL + 0, 0x56); rsu_write_1(sc, R92S_SPS0_CTRL + 1, 0x43); /* Firmware - tell it to switch things off */ (void) rsu_set_fw_power_state(sc, RSU_PWR_OFF); } static int rsu_fw_loadsection(struct rsu_softc *sc, const uint8_t *buf, int len) { const uint8_t which = rsu_wme_ac_xfer_map[WME_AC_VO]; struct rsu_data *data; struct r92s_tx_desc *txd; int mlen; while (len > 0) { data = rsu_getbuf(sc); if (data == NULL) return (ENOMEM); txd = (struct r92s_tx_desc *)data->buf; memset(txd, 0, sizeof(*txd)); if (len <= RSU_TXBUFSZ - sizeof(*txd)) { /* Last chunk. */ txd->txdw0 |= htole32(R92S_TXDW0_LINIP); mlen = len; } else mlen = RSU_TXBUFSZ - sizeof(*txd); txd->txdw0 |= htole32(SM(R92S_TXDW0_PKTLEN, mlen)); memcpy(&txd[1], buf, mlen); data->buflen = sizeof(*txd) + mlen; RSU_DPRINTF(sc, RSU_DEBUG_TX | RSU_DEBUG_FW | RSU_DEBUG_RESET, "%s: starting transfer %p\n", __func__, data); STAILQ_INSERT_TAIL(&sc->sc_tx_pending[which], data, next); buf += mlen; len -= mlen; } usbd_transfer_start(sc->sc_xfer[which]); return (0); } static int rsu_load_firmware(struct rsu_softc *sc) { const struct r92s_fw_hdr *hdr; struct r92s_fw_priv *dmem; struct ieee80211com *ic = &sc->sc_ic; const uint8_t *imem, *emem; int imemsz, ememsz; const struct firmware *fw; size_t size; uint32_t reg; int ntries, error; if (rsu_read_1(sc, R92S_TCR) & R92S_TCR_FWRDY) { RSU_DPRINTF(sc, RSU_DEBUG_ANY, "%s: Firmware already loaded\n", __func__); return (0); } RSU_UNLOCK(sc); /* Read firmware image from the filesystem. */ if ((fw = firmware_get("rsu-rtl8712fw")) == NULL) { device_printf(sc->sc_dev, "%s: failed load firmware of file rsu-rtl8712fw\n", __func__); RSU_LOCK(sc); return (ENXIO); } RSU_LOCK(sc); size = fw->datasize; if (size < sizeof(*hdr)) { device_printf(sc->sc_dev, "firmware too short\n"); error = EINVAL; goto fail; } hdr = (const struct r92s_fw_hdr *)fw->data; if (hdr->signature != htole16(0x8712) && hdr->signature != htole16(0x8192)) { device_printf(sc->sc_dev, "invalid firmware signature 0x%x\n", le16toh(hdr->signature)); error = EINVAL; goto fail; } DPRINTF("FW V%d %02x-%02x %02x:%02x\n", le16toh(hdr->version), hdr->month, hdr->day, hdr->hour, hdr->minute); /* Make sure that driver and firmware are in sync. */ if (hdr->privsz != htole32(sizeof(*dmem))) { device_printf(sc->sc_dev, "unsupported firmware image\n"); error = EINVAL; goto fail; } /* Get FW sections sizes. */ imemsz = le32toh(hdr->imemsz); ememsz = le32toh(hdr->sramsz); /* Check that all FW sections fit in image. */ if (size < sizeof(*hdr) + imemsz + ememsz) { device_printf(sc->sc_dev, "firmware too short\n"); error = EINVAL; goto fail; } imem = (const uint8_t *)&hdr[1]; emem = imem + imemsz; /* Load IMEM section. */ error = rsu_fw_loadsection(sc, imem, imemsz); if (error != 0) { device_printf(sc->sc_dev, "could not load firmware section %s\n", "IMEM"); goto fail; } /* Wait for load to complete. */ for (ntries = 0; ntries != 50; ntries++) { rsu_ms_delay(sc, 10); reg = rsu_read_1(sc, R92S_TCR); if (reg & R92S_TCR_IMEM_CODE_DONE) break; } if (ntries == 50) { device_printf(sc->sc_dev, "timeout waiting for IMEM transfer\n"); error = ETIMEDOUT; goto fail; } /* Load EMEM section. */ error = rsu_fw_loadsection(sc, emem, ememsz); if (error != 0) { device_printf(sc->sc_dev, "could not load firmware section %s\n", "EMEM"); goto fail; } /* Wait for load to complete. */ for (ntries = 0; ntries != 50; ntries++) { rsu_ms_delay(sc, 10); reg = rsu_read_2(sc, R92S_TCR); if (reg & R92S_TCR_EMEM_CODE_DONE) break; } if (ntries == 50) { device_printf(sc->sc_dev, "timeout waiting for EMEM transfer\n"); error = ETIMEDOUT; goto fail; } /* Enable CPU. */ rsu_write_1(sc, R92S_SYS_CLKR, rsu_read_1(sc, R92S_SYS_CLKR) | R92S_SYS_CPU_CLKSEL); if (!(rsu_read_1(sc, R92S_SYS_CLKR) & R92S_SYS_CPU_CLKSEL)) { device_printf(sc->sc_dev, "could not enable system clock\n"); error = EIO; goto fail; } rsu_write_2(sc, R92S_SYS_FUNC_EN, rsu_read_2(sc, R92S_SYS_FUNC_EN) | R92S_FEN_CPUEN); if (!(rsu_read_2(sc, R92S_SYS_FUNC_EN) & R92S_FEN_CPUEN)) { device_printf(sc->sc_dev, "could not enable microcontroller\n"); error = EIO; goto fail; } /* Wait for CPU to initialize. */ for (ntries = 0; ntries < 100; ntries++) { if (rsu_read_1(sc, R92S_TCR) & R92S_TCR_IMEM_RDY) break; rsu_ms_delay(sc, 1); } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for microcontroller\n"); error = ETIMEDOUT; goto fail; } /* Update DMEM section before loading. */ dmem = __DECONST(struct r92s_fw_priv *, &hdr->priv); memset(dmem, 0, sizeof(*dmem)); dmem->hci_sel = R92S_HCI_SEL_USB | R92S_HCI_SEL_8172; dmem->nendpoints = sc->sc_nendpoints; dmem->chip_version = sc->cut; dmem->rf_config = sc->sc_rftype; dmem->vcs_type = R92S_VCS_TYPE_AUTO; dmem->vcs_mode = R92S_VCS_MODE_RTS_CTS; dmem->turbo_mode = 0; dmem->bw40_en = !! (ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40); dmem->amsdu2ampdu_en = !! (sc->sc_ht); dmem->ampdu_en = !! (sc->sc_ht); dmem->agg_offload = !! (sc->sc_ht); dmem->qos_en = 1; dmem->ps_offload = 1; dmem->lowpower_mode = 1; /* XXX TODO: configurable? */ /* Load DMEM section. */ error = rsu_fw_loadsection(sc, (uint8_t *)dmem, sizeof(*dmem)); if (error != 0) { device_printf(sc->sc_dev, "could not load firmware section %s\n", "DMEM"); goto fail; } /* Wait for load to complete. */ for (ntries = 0; ntries < 100; ntries++) { if (rsu_read_1(sc, R92S_TCR) & R92S_TCR_DMEM_CODE_DONE) break; rsu_ms_delay(sc, 1); } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for %s transfer\n", "DMEM"); error = ETIMEDOUT; goto fail; } /* Wait for firmware readiness. */ for (ntries = 0; ntries < 60; ntries++) { if (!(rsu_read_1(sc, R92S_TCR) & R92S_TCR_FWRDY)) break; rsu_ms_delay(sc, 1); } if (ntries == 60) { device_printf(sc->sc_dev, "timeout waiting for firmware readiness\n"); error = ETIMEDOUT; goto fail; } fail: firmware_put(fw, FIRMWARE_UNLOAD); return (error); } static int rsu_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct rsu_softc *sc = ic->ic_softc; struct rsu_data *bf; /* prevent management frames from being sent if we're not ready */ if (!sc->sc_running) { m_freem(m); return (ENETDOWN); } RSU_LOCK(sc); bf = rsu_getbuf(sc); if (bf == NULL) { m_freem(m); RSU_UNLOCK(sc); return (ENOBUFS); } if (rsu_tx_start(sc, ni, m, bf) != 0) { m_freem(m); rsu_freebuf(sc, bf); RSU_UNLOCK(sc); return (EIO); } RSU_UNLOCK(sc); return (0); } static void rsu_init(struct rsu_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint8_t macaddr[IEEE80211_ADDR_LEN]; int error; int i; RSU_ASSERT_LOCKED(sc); /* Ensure the mbuf queue is drained */ rsu_drain_mbufq(sc); /* Init host async commands ring. */ sc->cmdq.cur = sc->cmdq.next = sc->cmdq.queued = 0; /* Reset power management state. */ rsu_write_1(sc, R92S_USB_HRPWM, 0); /* Power on adapter. */ if (sc->cut == 1) rsu_power_on_acut(sc); else rsu_power_on_bcut(sc); /* Load firmware. */ error = rsu_load_firmware(sc); if (error != 0) goto fail; /* Enable Rx TCP checksum offload. */ rsu_write_4(sc, R92S_RCR, rsu_read_4(sc, R92S_RCR) | 0x04000000); /* Append PHY status. */ rsu_write_4(sc, R92S_RCR, rsu_read_4(sc, R92S_RCR) | 0x02000000); rsu_write_4(sc, R92S_CR, rsu_read_4(sc, R92S_CR) & ~0xff000000); /* Use 128 bytes pages. */ rsu_write_1(sc, 0x00b5, rsu_read_1(sc, 0x00b5) | 0x01); /* Enable USB Rx aggregation. */ rsu_write_1(sc, 0x00bd, rsu_read_1(sc, 0x00bd) | 0x80); /* Set USB Rx aggregation threshold. */ rsu_write_1(sc, 0x00d9, 0x01); /* Set USB Rx aggregation timeout (1.7ms/4). */ rsu_write_1(sc, 0xfe5b, 0x04); /* Fix USB Rx FIFO issue. */ rsu_write_1(sc, 0xfe5c, rsu_read_1(sc, 0xfe5c) | 0x80); /* Set MAC address. */ IEEE80211_ADDR_COPY(macaddr, vap ? vap->iv_myaddr : ic->ic_macaddr); rsu_write_region_1(sc, R92S_MACID, macaddr, IEEE80211_ADDR_LEN); /* It really takes 1.5 seconds for the firmware to boot: */ rsu_ms_delay(sc, 2000); RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting MAC address to %s\n", __func__, ether_sprintf(macaddr)); error = rsu_fw_cmd(sc, R92S_CMD_SET_MAC_ADDRESS, macaddr, IEEE80211_ADDR_LEN); if (error != 0) { device_printf(sc->sc_dev, "could not set MAC address\n"); goto fail; } /* Set PS mode fully active */ error = rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); if (error != 0) { device_printf(sc->sc_dev, "could not set PS mode\n"); goto fail; } sc->sc_scan_pass = 0; usbd_transfer_start(sc->sc_xfer[RSU_BULK_RX]); /* We're ready to go. */ sc->sc_running = 1; sc->sc_scanning = 0; return; fail: /* Need to stop all failed transfers, if any */ for (i = 0; i != RSU_N_TRANSFER; i++) usbd_transfer_stop(sc->sc_xfer[i]); } static void rsu_stop(struct rsu_softc *sc) { int i; RSU_ASSERT_LOCKED(sc); sc->sc_running = 0; sc->sc_calibrating = 0; taskqueue_cancel_timeout(taskqueue_thread, &sc->calib_task, NULL); taskqueue_cancel(taskqueue_thread, &sc->tx_task, NULL); /* Power off adapter. */ rsu_power_off(sc); for (i = 0; i < RSU_N_TRANSFER; i++) usbd_transfer_stop(sc->sc_xfer[i]); /* Ensure the mbuf queue is drained */ rsu_drain_mbufq(sc); } /* * Note: usb_pause_mtx() actually releases the mutex before calling pause(), * which breaks any kind of driver serialisation. */ static void rsu_ms_delay(struct rsu_softc *sc, int ms) { //usb_pause_mtx(&sc->sc_mtx, hz / 1000); DELAY(ms * 1000); } Index: projects/release-pkg/sys/dev/xen/control/control.c =================================================================== --- projects/release-pkg/sys/dev/xen/control/control.c (revision 297604) +++ projects/release-pkg/sys/dev/xen/control/control.c (revision 297605) @@ -1,438 +1,438 @@ /*- * Copyright (c) 2010 Justin T. Gibbs, Spectra Logic 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, this list of conditions, and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * substantially 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 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. */ /*- * PV suspend/resume support: * * Copyright (c) 2004 Christian Limpach. * Copyright (c) 2004-2006,2008 Kip Macy * 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Christian Limpach. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /*- * HVM suspend/resume support: * * Copyright (c) 2008 Citrix Systems, Inc. * 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$"); /** * \file control.c * * \brief Device driver to repond to control domain events that impact * this VM. */ #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 /*--------------------------- Forward Declarations --------------------------*/ /** Function signature for shutdown event handlers. */ typedef void (xctrl_shutdown_handler_t)(void); static xctrl_shutdown_handler_t xctrl_poweroff; static xctrl_shutdown_handler_t xctrl_reboot; static xctrl_shutdown_handler_t xctrl_suspend; static xctrl_shutdown_handler_t xctrl_crash; /*-------------------------- Private Data Structures -------------------------*/ /** Element type for lookup table of event name to handler. */ struct xctrl_shutdown_reason { const char *name; xctrl_shutdown_handler_t *handler; }; /** Lookup table for shutdown event name to handler. */ static const struct xctrl_shutdown_reason xctrl_shutdown_reasons[] = { { "poweroff", xctrl_poweroff }, { "reboot", xctrl_reboot }, { "suspend", xctrl_suspend }, { "crash", xctrl_crash }, { "halt", xctrl_poweroff }, }; struct xctrl_softc { struct xs_watch xctrl_watch; }; /*------------------------------ Event Handlers ------------------------------*/ static void xctrl_poweroff() { shutdown_nice(RB_POWEROFF|RB_HALT); } static void xctrl_reboot() { shutdown_nice(0); } static void xctrl_suspend() { #ifdef SMP cpuset_t cpu_suspend_map; #endif int suspend_cancelled; EVENTHANDLER_INVOKE(power_suspend); if (smp_started) { thread_lock(curthread); sched_bind(curthread, 0); thread_unlock(curthread); } KASSERT((PCPU_GET(cpuid) == 0), ("Not running on CPU#0")); /* * Clear our XenStore node so the toolstack knows we are * responding to the suspend request. */ xs_write(XST_NIL, "control", "shutdown", ""); /* * Be sure to hold Giant across DEVICE_SUSPEND/RESUME since non-MPSAFE * drivers need this. */ mtx_lock(&Giant); if (DEVICE_SUSPEND(root_bus) != 0) { mtx_unlock(&Giant); printf("%s: device_suspend failed\n", __func__); return; } mtx_unlock(&Giant); #ifdef SMP CPU_ZERO(&cpu_suspend_map); /* silence gcc */ if (smp_started) { /* * Suspend other CPUs. This prevents IPIs while we * are resuming, and will allow us to reset per-cpu * vcpu_info on resume. */ cpu_suspend_map = all_cpus; CPU_CLR(PCPU_GET(cpuid), &cpu_suspend_map); if (!CPU_EMPTY(&cpu_suspend_map)) suspend_cpus(cpu_suspend_map); } #endif /* * Prevent any races with evtchn_interrupt() handler. */ disable_intr(); intr_suspend(); xen_hvm_suspend(); suspend_cancelled = HYPERVISOR_suspend(0); xen_hvm_resume(suspend_cancelled != 0); intr_resume(suspend_cancelled != 0); enable_intr(); /* * Reset grant table info. */ gnttab_resume(NULL); #ifdef SMP /* Send an IPI_BITMAP in case there are pending bitmap IPIs. */ lapic_ipi_vectored(IPI_BITMAP_VECTOR, APIC_IPI_DEST_ALL); - if (smp_started && !CPU_EMPTY(&cpu_suspend_map)) { + if (!CPU_EMPTY(&cpu_suspend_map)) { /* * Now that event channels have been initialized, * resume CPUs. */ resume_cpus(cpu_suspend_map); } #endif /* * FreeBSD really needs to add DEVICE_SUSPEND_CANCEL or * similar. */ mtx_lock(&Giant); DEVICE_RESUME(root_bus); mtx_unlock(&Giant); if (smp_started) { thread_lock(curthread); sched_unbind(curthread); thread_unlock(curthread); } EVENTHANDLER_INVOKE(power_resume); if (bootverbose) printf("System resumed after suspension\n"); } static void xctrl_crash() { panic("Xen directed crash"); } static void xen_pv_shutdown_final(void *arg, int howto) { /* * Inform the hypervisor that shutdown is complete. * This is not necessary in HVM domains since Xen * emulates ACPI in that mode and FreeBSD's ACPI * support will request this transition. */ if (howto & (RB_HALT | RB_POWEROFF)) HYPERVISOR_shutdown(SHUTDOWN_poweroff); else HYPERVISOR_shutdown(SHUTDOWN_reboot); } /*------------------------------ Event Reception -----------------------------*/ static void xctrl_on_watch_event(struct xs_watch *watch, const char **vec, unsigned int len) { const struct xctrl_shutdown_reason *reason; const struct xctrl_shutdown_reason *last_reason; char *result; int error; int result_len; error = xs_read(XST_NIL, "control", "shutdown", &result_len, (void **)&result); if (error != 0) return; reason = xctrl_shutdown_reasons; last_reason = reason + nitems(xctrl_shutdown_reasons); while (reason < last_reason) { if (!strcmp(result, reason->name)) { reason->handler(); break; } reason++; } free(result, M_XENSTORE); } /*------------------ Private Device Attachment Functions --------------------*/ /** * \brief Identify instances of this device type in the system. * * \param driver The driver performing this identify action. * \param parent The NewBus parent device for any devices this method adds. */ static void xctrl_identify(driver_t *driver __unused, device_t parent) { /* * A single device instance for our driver is always present * in a system operating under Xen. */ BUS_ADD_CHILD(parent, 0, driver->name, 0); } /** * \brief Probe for the existance of the Xen Control device * * \param dev NewBus device_t for this Xen control instance. * * \return Always returns 0 indicating success. */ static int xctrl_probe(device_t dev) { device_set_desc(dev, "Xen Control Device"); return (BUS_PROBE_NOWILDCARD); } /** * \brief Attach the Xen control device. * * \param dev NewBus device_t for this Xen control instance. * * \return On success, 0. Otherwise an errno value indicating the * type of failure. */ static int xctrl_attach(device_t dev) { struct xctrl_softc *xctrl; xctrl = device_get_softc(dev); /* Activate watch */ xctrl->xctrl_watch.node = "control/shutdown"; xctrl->xctrl_watch.callback = xctrl_on_watch_event; xctrl->xctrl_watch.callback_data = (uintptr_t)xctrl; xs_register_watch(&xctrl->xctrl_watch); if (xen_pv_domain()) EVENTHANDLER_REGISTER(shutdown_final, xen_pv_shutdown_final, NULL, SHUTDOWN_PRI_LAST); return (0); } /** * \brief Detach the Xen control device. * * \param dev NewBus device_t for this Xen control device instance. * * \return On success, 0. Otherwise an errno value indicating the * type of failure. */ static int xctrl_detach(device_t dev) { struct xctrl_softc *xctrl; xctrl = device_get_softc(dev); /* Release watch */ xs_unregister_watch(&xctrl->xctrl_watch); return (0); } /*-------------------- Private Device Attachment Data -----------------------*/ static device_method_t xctrl_methods[] = { /* Device interface */ DEVMETHOD(device_identify, xctrl_identify), DEVMETHOD(device_probe, xctrl_probe), DEVMETHOD(device_attach, xctrl_attach), DEVMETHOD(device_detach, xctrl_detach), DEVMETHOD_END }; DEFINE_CLASS_0(xctrl, xctrl_driver, xctrl_methods, sizeof(struct xctrl_softc)); devclass_t xctrl_devclass; DRIVER_MODULE(xctrl, xenstore, xctrl_driver, xctrl_devclass, NULL, NULL); Index: projects/release-pkg/sys/i386/linux/linux_sysvec.c =================================================================== --- projects/release-pkg/sys/i386/linux/linux_sysvec.c (revision 297604) +++ projects/release-pkg/sys/i386/linux/linux_sysvec.c (revision 297605) @@ -1,1199 +1,1200 @@ /*- * Copyright (c) 1994-1996 Søren Schmidt * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #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 MODULE_VERSION(linux, 1); #if BYTE_ORDER == LITTLE_ENDIAN #define SHELLMAGIC 0x2123 /* #! */ #else #define SHELLMAGIC 0x2321 #endif #if defined(DEBUG) SYSCTL_PROC(_compat_linux, OID_AUTO, debug, CTLTYPE_STRING | CTLFLAG_RW, 0, 0, linux_sysctl_debug, "A", "Linux debugging control"); #endif /* * Allow the sendsig functions to use the ldebug() facility * even though they are not syscalls themselves. Map them * to syscall 0. This is slightly less bogus than using * ldebug(sigreturn). */ #define LINUX_SYS_linux_rt_sendsig 0 #define LINUX_SYS_linux_sendsig 0 #define LINUX_PS_STRINGS (LINUX_USRSTACK - sizeof(struct ps_strings)) static int linux_szsigcode; static vm_object_t linux_shared_page_obj; static char *linux_shared_page_mapping; extern char _binary_linux_locore_o_start; extern char _binary_linux_locore_o_end; extern struct sysent linux_sysent[LINUX_SYS_MAXSYSCALL]; SET_DECLARE(linux_ioctl_handler_set, struct linux_ioctl_handler); static int linux_fixup(register_t **stack_base, struct image_params *iparams); static int elf_linux_fixup(register_t **stack_base, struct image_params *iparams); static void linux_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask); static void exec_linux_setregs(struct thread *td, struct image_params *imgp, u_long stack); static register_t *linux_copyout_strings(struct image_params *imgp); static boolean_t linux_trans_osrel(const Elf_Note *note, int32_t *osrel); static void linux_vdso_install(void *param); static void linux_vdso_deinstall(void *param); static int linux_szplatform; const char *linux_kplatform; static eventhandler_tag linux_exit_tag; static eventhandler_tag linux_exec_tag; static eventhandler_tag linux_thread_dtor_tag; /* * Linux syscalls return negative errno's, we do positive and map them * Reference: * FreeBSD: src/sys/sys/errno.h * Linux: linux-2.6.17.8/include/asm-generic/errno-base.h * linux-2.6.17.8/include/asm-generic/errno.h */ static int bsd_to_linux_errno[ELAST + 1] = { -0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -35, -12, -13, -14, -15, -16, -17, -18, -19, -20, -21, -22, -23, -24, -25, -26, -27, -28, -29, -30, -31, -32, -33, -34, -11,-115,-114, -88, -89, -90, -91, -92, -93, -94, -95, -96, -97, -98, -99, -100,-101,-102,-103,-104,-105,-106,-107,-108,-109, -110,-111, -40, -36,-112,-113, -39, -11, -87,-122, -116, -66, -6, -6, -6, -6, -6, -37, -38, -9, -6, -6, -43, -42, -75,-125, -84, -95, -16, -74, -72, -67, -71 }; #define LINUX_T_UNKNOWN 255 static int _bsd_to_linux_trapcode[] = { LINUX_T_UNKNOWN, /* 0 */ 6, /* 1 T_PRIVINFLT */ LINUX_T_UNKNOWN, /* 2 */ 3, /* 3 T_BPTFLT */ LINUX_T_UNKNOWN, /* 4 */ LINUX_T_UNKNOWN, /* 5 */ 16, /* 6 T_ARITHTRAP */ 254, /* 7 T_ASTFLT */ LINUX_T_UNKNOWN, /* 8 */ 13, /* 9 T_PROTFLT */ 1, /* 10 T_TRCTRAP */ LINUX_T_UNKNOWN, /* 11 */ 14, /* 12 T_PAGEFLT */ LINUX_T_UNKNOWN, /* 13 */ 17, /* 14 T_ALIGNFLT */ LINUX_T_UNKNOWN, /* 15 */ LINUX_T_UNKNOWN, /* 16 */ LINUX_T_UNKNOWN, /* 17 */ 0, /* 18 T_DIVIDE */ 2, /* 19 T_NMI */ 4, /* 20 T_OFLOW */ 5, /* 21 T_BOUND */ 7, /* 22 T_DNA */ 8, /* 23 T_DOUBLEFLT */ 9, /* 24 T_FPOPFLT */ 10, /* 25 T_TSSFLT */ 11, /* 26 T_SEGNPFLT */ 12, /* 27 T_STKFLT */ 18, /* 28 T_MCHK */ 19, /* 29 T_XMMFLT */ 15 /* 30 T_RESERVED */ }; #define bsd_to_linux_trapcode(code) \ ((code)args->argc + 1); (*stack_base)--; suword(*stack_base, (intptr_t)(void *)envp); (*stack_base)--; suword(*stack_base, (intptr_t)(void *)argv); (*stack_base)--; suword(*stack_base, imgp->args->argc); return (0); } static int elf_linux_fixup(register_t **stack_base, struct image_params *imgp) { struct proc *p; Elf32_Auxargs *args; Elf32_Addr *uplatform; struct ps_strings *arginfo; register_t *pos; int issetugid; KASSERT(curthread->td_proc == imgp->proc, ("unsafe elf_linux_fixup(), should be curproc")); p = imgp->proc; issetugid = imgp->proc->p_flag & P_SUGID ? 1 : 0; arginfo = (struct ps_strings *)p->p_sysent->sv_psstrings; uplatform = (Elf32_Addr *)((caddr_t)arginfo - linux_szplatform); args = (Elf32_Auxargs *)imgp->auxargs; pos = *stack_base + (imgp->args->argc + imgp->args->envc + 2); AUXARGS_ENTRY(pos, LINUX_AT_SYSINFO_EHDR, imgp->proc->p_sysent->sv_shared_page_base); AUXARGS_ENTRY(pos, LINUX_AT_SYSINFO, linux_vsyscall); AUXARGS_ENTRY(pos, LINUX_AT_HWCAP, cpu_feature); /* * Do not export AT_CLKTCK when emulating Linux kernel prior to 2.4.0, * as it has appeared in the 2.4.0-rc7 first time. * Being exported, AT_CLKTCK is returned by sysconf(_SC_CLK_TCK), * glibc falls back to the hard-coded CLK_TCK value when aux entry * is not present. * Also see linux_times() implementation. */ if (linux_kernver(curthread) >= LINUX_KERNVER_2004000) AUXARGS_ENTRY(pos, LINUX_AT_CLKTCK, stclohz); AUXARGS_ENTRY(pos, AT_PHDR, args->phdr); AUXARGS_ENTRY(pos, AT_PHENT, args->phent); AUXARGS_ENTRY(pos, AT_PHNUM, args->phnum); AUXARGS_ENTRY(pos, AT_PAGESZ, args->pagesz); AUXARGS_ENTRY(pos, AT_FLAGS, args->flags); AUXARGS_ENTRY(pos, AT_ENTRY, args->entry); AUXARGS_ENTRY(pos, AT_BASE, args->base); AUXARGS_ENTRY(pos, LINUX_AT_SECURE, issetugid); AUXARGS_ENTRY(pos, AT_UID, imgp->proc->p_ucred->cr_ruid); AUXARGS_ENTRY(pos, AT_EUID, imgp->proc->p_ucred->cr_svuid); AUXARGS_ENTRY(pos, AT_GID, imgp->proc->p_ucred->cr_rgid); AUXARGS_ENTRY(pos, AT_EGID, imgp->proc->p_ucred->cr_svgid); AUXARGS_ENTRY(pos, LINUX_AT_PLATFORM, PTROUT(uplatform)); AUXARGS_ENTRY(pos, LINUX_AT_RANDOM, imgp->canary); if (imgp->execpathp != 0) AUXARGS_ENTRY(pos, LINUX_AT_EXECFN, imgp->execpathp); if (args->execfd != -1) AUXARGS_ENTRY(pos, AT_EXECFD, args->execfd); AUXARGS_ENTRY(pos, AT_NULL, 0); free(imgp->auxargs, M_TEMP); imgp->auxargs = NULL; (*stack_base)--; suword(*stack_base, (register_t)imgp->args->argc); return (0); } /* * Copied from kern/kern_exec.c */ static register_t * linux_copyout_strings(struct image_params *imgp) { int argc, envc; char **vectp; char *stringp, *destp; register_t *stack_base; struct ps_strings *arginfo; char canary[LINUX_AT_RANDOM_LEN]; size_t execpath_len; struct proc *p; /* * Calculate string base and vector table pointers. */ p = imgp->proc; if (imgp->execpath != NULL && imgp->auxargs != NULL) execpath_len = strlen(imgp->execpath) + 1; else execpath_len = 0; arginfo = (struct ps_strings *)p->p_sysent->sv_psstrings; destp = (caddr_t)arginfo - SPARE_USRSPACE - linux_szplatform - roundup(sizeof(canary), sizeof(char *)) - roundup(execpath_len, sizeof(char *)) - roundup((ARG_MAX - imgp->args->stringspace), sizeof(char *)); /* * install LINUX_PLATFORM */ copyout(linux_kplatform, ((caddr_t)arginfo - linux_szplatform), linux_szplatform); if (execpath_len != 0) { imgp->execpathp = (uintptr_t)arginfo - linux_szplatform - execpath_len; copyout(imgp->execpath, (void *)imgp->execpathp, execpath_len); } /* * Prepare the canary for SSP. */ arc4rand(canary, sizeof(canary), 0); imgp->canary = (uintptr_t)arginfo - linux_szplatform - roundup(execpath_len, sizeof(char *)) - roundup(sizeof(canary), sizeof(char *)); copyout(canary, (void *)imgp->canary, sizeof(canary)); /* * If we have a valid auxargs ptr, prepare some room * on the stack. */ if (imgp->auxargs) { /* * 'AT_COUNT*2' is size for the ELF Auxargs data. This is for * lower compatibility. */ imgp->auxarg_size = (imgp->auxarg_size) ? imgp->auxarg_size : (LINUX_AT_COUNT * 2); /* * The '+ 2' is for the null pointers at the end of each of * the arg and env vector sets,and imgp->auxarg_size is room * for argument of Runtime loader. */ vectp = (char **)(destp - (imgp->args->argc + imgp->args->envc + 2 + imgp->auxarg_size) * sizeof(char *)); } else { /* * The '+ 2' is for the null pointers at the end of each of * the arg and env vector sets */ vectp = (char **)(destp - (imgp->args->argc + imgp->args->envc + 2) * sizeof(char *)); } /* * vectp also becomes our initial stack base */ stack_base = (register_t *)vectp; stringp = imgp->args->begin_argv; argc = imgp->args->argc; envc = imgp->args->envc; /* * Copy out strings - arguments and environment. */ copyout(stringp, destp, ARG_MAX - imgp->args->stringspace); /* * Fill in "ps_strings" struct for ps, w, etc. */ suword(&arginfo->ps_argvstr, (long)(intptr_t)vectp); suword(&arginfo->ps_nargvstr, argc); /* * Fill in argument portion of vector table. */ for (; argc > 0; --argc) { suword(vectp++, (long)(intptr_t)destp); while (*stringp++ != 0) destp++; destp++; } /* a null vector table pointer separates the argp's from the envp's */ suword(vectp++, 0); suword(&arginfo->ps_envstr, (long)(intptr_t)vectp); suword(&arginfo->ps_nenvstr, envc); /* * Fill in environment portion of vector table. */ for (; envc > 0; --envc) { suword(vectp++, (long)(intptr_t)destp); while (*stringp++ != 0) destp++; destp++; } /* end of vector table is a null pointer */ suword(vectp, 0); return (stack_base); } static void linux_rt_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask) { struct thread *td = curthread; struct proc *p = td->td_proc; struct sigacts *psp; struct trapframe *regs; struct l_rt_sigframe *fp, frame; int sig, code; int oonstack; sig = ksi->ksi_signo; code = ksi->ksi_code; PROC_LOCK_ASSERT(p, MA_OWNED); psp = p->p_sigacts; mtx_assert(&psp->ps_mtx, MA_OWNED); regs = td->td_frame; oonstack = sigonstack(regs->tf_esp); #ifdef DEBUG if (ldebug(rt_sendsig)) printf(ARGS(rt_sendsig, "%p, %d, %p, %u"), catcher, sig, (void*)mask, code); #endif /* * Allocate space for the signal handler context. */ if ((td->td_pflags & TDP_ALTSTACK) && !oonstack && SIGISMEMBER(psp->ps_sigonstack, sig)) { fp = (struct l_rt_sigframe *)((uintptr_t)td->td_sigstk.ss_sp + td->td_sigstk.ss_size - sizeof(struct l_rt_sigframe)); } else fp = (struct l_rt_sigframe *)regs->tf_esp - 1; mtx_unlock(&psp->ps_mtx); /* * Build the argument list for the signal handler. */ sig = bsd_to_linux_signal(sig); bzero(&frame, sizeof(frame)); frame.sf_handler = catcher; frame.sf_sig = sig; frame.sf_siginfo = &fp->sf_si; frame.sf_ucontext = &fp->sf_sc; /* Fill in POSIX parts */ ksiginfo_to_lsiginfo(ksi, &frame.sf_si, sig); /* * Build the signal context to be used by sigreturn. */ frame.sf_sc.uc_flags = 0; /* XXX ??? */ frame.sf_sc.uc_link = NULL; /* XXX ??? */ frame.sf_sc.uc_stack.ss_sp = td->td_sigstk.ss_sp; frame.sf_sc.uc_stack.ss_size = td->td_sigstk.ss_size; frame.sf_sc.uc_stack.ss_flags = (td->td_pflags & TDP_ALTSTACK) ? ((oonstack) ? LINUX_SS_ONSTACK : 0) : LINUX_SS_DISABLE; PROC_UNLOCK(p); bsd_to_linux_sigset(mask, &frame.sf_sc.uc_sigmask); frame.sf_sc.uc_mcontext.sc_mask = frame.sf_sc.uc_sigmask.__mask; frame.sf_sc.uc_mcontext.sc_gs = rgs(); frame.sf_sc.uc_mcontext.sc_fs = regs->tf_fs; frame.sf_sc.uc_mcontext.sc_es = regs->tf_es; frame.sf_sc.uc_mcontext.sc_ds = regs->tf_ds; frame.sf_sc.uc_mcontext.sc_edi = regs->tf_edi; frame.sf_sc.uc_mcontext.sc_esi = regs->tf_esi; frame.sf_sc.uc_mcontext.sc_ebp = regs->tf_ebp; frame.sf_sc.uc_mcontext.sc_ebx = regs->tf_ebx; frame.sf_sc.uc_mcontext.sc_esp = regs->tf_esp; frame.sf_sc.uc_mcontext.sc_edx = regs->tf_edx; frame.sf_sc.uc_mcontext.sc_ecx = regs->tf_ecx; frame.sf_sc.uc_mcontext.sc_eax = regs->tf_eax; frame.sf_sc.uc_mcontext.sc_eip = regs->tf_eip; frame.sf_sc.uc_mcontext.sc_cs = regs->tf_cs; frame.sf_sc.uc_mcontext.sc_eflags = regs->tf_eflags; frame.sf_sc.uc_mcontext.sc_esp_at_signal = regs->tf_esp; frame.sf_sc.uc_mcontext.sc_ss = regs->tf_ss; frame.sf_sc.uc_mcontext.sc_err = regs->tf_err; frame.sf_sc.uc_mcontext.sc_cr2 = (register_t)ksi->ksi_addr; frame.sf_sc.uc_mcontext.sc_trapno = bsd_to_linux_trapcode(code); #ifdef DEBUG if (ldebug(rt_sendsig)) printf(LMSG("rt_sendsig flags: 0x%x, sp: %p, ss: 0x%x, mask: 0x%x"), frame.sf_sc.uc_stack.ss_flags, td->td_sigstk.ss_sp, td->td_sigstk.ss_size, frame.sf_sc.uc_mcontext.sc_mask); #endif if (copyout(&frame, fp, sizeof(frame)) != 0) { /* * Process has trashed its stack; give it an illegal * instruction to halt it in its tracks. */ #ifdef DEBUG if (ldebug(rt_sendsig)) printf(LMSG("rt_sendsig: bad stack %p, oonstack=%x"), fp, oonstack); #endif PROC_LOCK(p); sigexit(td, SIGILL); } /* * Build context to run handler in. */ regs->tf_esp = (int)fp; regs->tf_eip = linux_rt_sigcode; regs->tf_eflags &= ~(PSL_T | PSL_VM | PSL_D); regs->tf_cs = _ucodesel; regs->tf_ds = _udatasel; regs->tf_es = _udatasel; regs->tf_fs = _udatasel; regs->tf_ss = _udatasel; PROC_LOCK(p); mtx_lock(&psp->ps_mtx); } /* * Send an interrupt to process. * * Stack is set up to allow sigcode stored * in u. to call routine, followed by kcall * to sigreturn routine below. After sigreturn * resets the signal mask, the stack, and the * frame pointer, it returns to the user * specified pc, psl. */ static void linux_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask) { struct thread *td = curthread; struct proc *p = td->td_proc; struct sigacts *psp; struct trapframe *regs; struct l_sigframe *fp, frame; l_sigset_t lmask; int sig, code; int oonstack; PROC_LOCK_ASSERT(p, MA_OWNED); psp = p->p_sigacts; sig = ksi->ksi_signo; code = ksi->ksi_code; mtx_assert(&psp->ps_mtx, MA_OWNED); if (SIGISMEMBER(psp->ps_siginfo, sig)) { /* Signal handler installed with SA_SIGINFO. */ linux_rt_sendsig(catcher, ksi, mask); return; } regs = td->td_frame; oonstack = sigonstack(regs->tf_esp); #ifdef DEBUG if (ldebug(sendsig)) printf(ARGS(sendsig, "%p, %d, %p, %u"), catcher, sig, (void*)mask, code); #endif /* * Allocate space for the signal handler context. */ if ((td->td_pflags & TDP_ALTSTACK) && !oonstack && SIGISMEMBER(psp->ps_sigonstack, sig)) { fp = (struct l_sigframe *)((uintptr_t)td->td_sigstk.ss_sp + td->td_sigstk.ss_size - sizeof(struct l_sigframe)); } else fp = (struct l_sigframe *)regs->tf_esp - 1; mtx_unlock(&psp->ps_mtx); PROC_UNLOCK(p); /* * Build the argument list for the signal handler. */ sig = bsd_to_linux_signal(sig); bzero(&frame, sizeof(frame)); frame.sf_handler = catcher; frame.sf_sig = sig; bsd_to_linux_sigset(mask, &lmask); /* * Build the signal context to be used by sigreturn. */ frame.sf_sc.sc_mask = lmask.__mask; frame.sf_sc.sc_gs = rgs(); frame.sf_sc.sc_fs = regs->tf_fs; frame.sf_sc.sc_es = regs->tf_es; frame.sf_sc.sc_ds = regs->tf_ds; frame.sf_sc.sc_edi = regs->tf_edi; frame.sf_sc.sc_esi = regs->tf_esi; frame.sf_sc.sc_ebp = regs->tf_ebp; frame.sf_sc.sc_ebx = regs->tf_ebx; frame.sf_sc.sc_esp = regs->tf_esp; frame.sf_sc.sc_edx = regs->tf_edx; frame.sf_sc.sc_ecx = regs->tf_ecx; frame.sf_sc.sc_eax = regs->tf_eax; frame.sf_sc.sc_eip = regs->tf_eip; frame.sf_sc.sc_cs = regs->tf_cs; frame.sf_sc.sc_eflags = regs->tf_eflags; frame.sf_sc.sc_esp_at_signal = regs->tf_esp; frame.sf_sc.sc_ss = regs->tf_ss; frame.sf_sc.sc_err = regs->tf_err; frame.sf_sc.sc_cr2 = (register_t)ksi->ksi_addr; frame.sf_sc.sc_trapno = bsd_to_linux_trapcode(ksi->ksi_trapno); frame.sf_extramask[0] = lmask.__mask; if (copyout(&frame, fp, sizeof(frame)) != 0) { /* * Process has trashed its stack; give it an illegal * instruction to halt it in its tracks. */ PROC_LOCK(p); sigexit(td, SIGILL); } /* * Build context to run handler in. */ regs->tf_esp = (int)fp; regs->tf_eip = linux_sigcode; regs->tf_eflags &= ~(PSL_T | PSL_VM | PSL_D); regs->tf_cs = _ucodesel; regs->tf_ds = _udatasel; regs->tf_es = _udatasel; regs->tf_fs = _udatasel; regs->tf_ss = _udatasel; PROC_LOCK(p); mtx_lock(&psp->ps_mtx); } /* * System call to cleanup state after a signal * has been taken. Reset signal mask and * stack state from context left by sendsig (above). * Return to previous pc and psl as specified by * context left by sendsig. Check carefully to * make sure that the user has not modified the * psl to gain improper privileges or to cause * a machine fault. */ int linux_sigreturn(struct thread *td, struct linux_sigreturn_args *args) { struct l_sigframe frame; struct trapframe *regs; l_sigset_t lmask; sigset_t bmask; int eflags; ksiginfo_t ksi; regs = td->td_frame; #ifdef DEBUG if (ldebug(sigreturn)) printf(ARGS(sigreturn, "%p"), (void *)args->sfp); #endif /* * The trampoline code hands us the sigframe. * It is unsafe to keep track of it ourselves, in the event that a * program jumps out of a signal handler. */ if (copyin(args->sfp, &frame, sizeof(frame)) != 0) return (EFAULT); /* * Check for security violations. */ #define EFLAGS_SECURE(ef, oef) ((((ef) ^ (oef)) & ~PSL_USERCHANGE) == 0) eflags = frame.sf_sc.sc_eflags; if (!EFLAGS_SECURE(eflags, regs->tf_eflags)) return (EINVAL); /* * Don't allow users to load a valid privileged %cs. Let the * hardware check for invalid selectors, excess privilege in * other selectors, invalid %eip's and invalid %esp's. */ #define CS_SECURE(cs) (ISPL(cs) == SEL_UPL) if (!CS_SECURE(frame.sf_sc.sc_cs)) { ksiginfo_init_trap(&ksi); ksi.ksi_signo = SIGBUS; ksi.ksi_code = BUS_OBJERR; ksi.ksi_trapno = T_PROTFLT; ksi.ksi_addr = (void *)regs->tf_eip; trapsignal(td, &ksi); return (EINVAL); } lmask.__mask = frame.sf_sc.sc_mask; linux_to_bsd_sigset(&lmask, &bmask); kern_sigprocmask(td, SIG_SETMASK, &bmask, NULL, 0); /* * Restore signal context. */ /* %gs was restored by the trampoline. */ regs->tf_fs = frame.sf_sc.sc_fs; regs->tf_es = frame.sf_sc.sc_es; regs->tf_ds = frame.sf_sc.sc_ds; regs->tf_edi = frame.sf_sc.sc_edi; regs->tf_esi = frame.sf_sc.sc_esi; regs->tf_ebp = frame.sf_sc.sc_ebp; regs->tf_ebx = frame.sf_sc.sc_ebx; regs->tf_edx = frame.sf_sc.sc_edx; regs->tf_ecx = frame.sf_sc.sc_ecx; regs->tf_eax = frame.sf_sc.sc_eax; regs->tf_eip = frame.sf_sc.sc_eip; regs->tf_cs = frame.sf_sc.sc_cs; regs->tf_eflags = eflags; regs->tf_esp = frame.sf_sc.sc_esp_at_signal; regs->tf_ss = frame.sf_sc.sc_ss; return (EJUSTRETURN); } /* * System call to cleanup state after a signal * has been taken. Reset signal mask and * stack state from context left by rt_sendsig (above). * Return to previous pc and psl as specified by * context left by sendsig. Check carefully to * make sure that the user has not modified the * psl to gain improper privileges or to cause * a machine fault. */ int linux_rt_sigreturn(struct thread *td, struct linux_rt_sigreturn_args *args) { struct l_ucontext uc; struct l_sigcontext *context; sigset_t bmask; l_stack_t *lss; stack_t ss; struct trapframe *regs; int eflags; ksiginfo_t ksi; regs = td->td_frame; #ifdef DEBUG if (ldebug(rt_sigreturn)) printf(ARGS(rt_sigreturn, "%p"), (void *)args->ucp); #endif /* * The trampoline code hands us the ucontext. * It is unsafe to keep track of it ourselves, in the event that a * program jumps out of a signal handler. */ if (copyin(args->ucp, &uc, sizeof(uc)) != 0) return (EFAULT); context = &uc.uc_mcontext; /* * Check for security violations. */ #define EFLAGS_SECURE(ef, oef) ((((ef) ^ (oef)) & ~PSL_USERCHANGE) == 0) eflags = context->sc_eflags; if (!EFLAGS_SECURE(eflags, regs->tf_eflags)) return (EINVAL); /* * Don't allow users to load a valid privileged %cs. Let the * hardware check for invalid selectors, excess privilege in * other selectors, invalid %eip's and invalid %esp's. */ #define CS_SECURE(cs) (ISPL(cs) == SEL_UPL) if (!CS_SECURE(context->sc_cs)) { ksiginfo_init_trap(&ksi); ksi.ksi_signo = SIGBUS; ksi.ksi_code = BUS_OBJERR; ksi.ksi_trapno = T_PROTFLT; ksi.ksi_addr = (void *)regs->tf_eip; trapsignal(td, &ksi); return (EINVAL); } linux_to_bsd_sigset(&uc.uc_sigmask, &bmask); kern_sigprocmask(td, SIG_SETMASK, &bmask, NULL, 0); /* * Restore signal context */ /* %gs was restored by the trampoline. */ regs->tf_fs = context->sc_fs; regs->tf_es = context->sc_es; regs->tf_ds = context->sc_ds; regs->tf_edi = context->sc_edi; regs->tf_esi = context->sc_esi; regs->tf_ebp = context->sc_ebp; regs->tf_ebx = context->sc_ebx; regs->tf_edx = context->sc_edx; regs->tf_ecx = context->sc_ecx; regs->tf_eax = context->sc_eax; regs->tf_eip = context->sc_eip; regs->tf_cs = context->sc_cs; regs->tf_eflags = eflags; regs->tf_esp = context->sc_esp_at_signal; regs->tf_ss = context->sc_ss; /* * call sigaltstack & ignore results.. */ lss = &uc.uc_stack; ss.ss_sp = lss->ss_sp; ss.ss_size = lss->ss_size; ss.ss_flags = linux_to_bsd_sigaltstack(lss->ss_flags); #ifdef DEBUG if (ldebug(rt_sigreturn)) printf(LMSG("rt_sigret flags: 0x%x, sp: %p, ss: 0x%x, mask: 0x%x"), ss.ss_flags, ss.ss_sp, ss.ss_size, context->sc_mask); #endif (void)kern_sigaltstack(td, &ss, NULL); return (EJUSTRETURN); } static int linux_fetch_syscall_args(struct thread *td, struct syscall_args *sa) { struct proc *p; struct trapframe *frame; p = td->td_proc; frame = td->td_frame; sa->code = frame->tf_eax; sa->args[0] = frame->tf_ebx; sa->args[1] = frame->tf_ecx; sa->args[2] = frame->tf_edx; sa->args[3] = frame->tf_esi; sa->args[4] = frame->tf_edi; sa->args[5] = frame->tf_ebp; /* Unconfirmed */ if (sa->code >= p->p_sysent->sv_size) /* nosys */ sa->callp = &p->p_sysent->sv_table[p->p_sysent->sv_size - 1]; else sa->callp = &p->p_sysent->sv_table[sa->code]; sa->narg = sa->callp->sy_narg; td->td_retval[0] = 0; td->td_retval[1] = frame->tf_edx; return (0); } /* * If a linux binary is exec'ing something, try this image activator * first. We override standard shell script execution in order to * be able to modify the interpreter path. We only do this if a linux * binary is doing the exec, so we do not create an EXEC module for it. */ static int exec_linux_imgact_try(struct image_params *iparams); static int exec_linux_imgact_try(struct image_params *imgp) { const char *head = (const char *)imgp->image_header; char *rpath; int error = -1; /* * The interpreter for shell scripts run from a linux binary needs * to be located in /compat/linux if possible in order to recursively * maintain linux path emulation. */ if (((const short *)head)[0] == SHELLMAGIC) { /* * Run our normal shell image activator. If it succeeds attempt * to use the alternate path for the interpreter. If an alternate * path is found, use our stringspace to store it. */ if ((error = exec_shell_imgact(imgp)) == 0) { linux_emul_convpath(FIRST_THREAD_IN_PROC(imgp->proc), imgp->interpreter_name, UIO_SYSSPACE, &rpath, 0, AT_FDCWD); if (rpath != NULL) imgp->args->fname_buf = imgp->interpreter_name = rpath; } } return (error); } /* * exec_setregs may initialize some registers differently than Linux * does, thus potentially confusing Linux binaries. If necessary, we * override the exec_setregs default(s) here. */ static void exec_linux_setregs(struct thread *td, struct image_params *imgp, u_long stack) { struct pcb *pcb = td->td_pcb; exec_setregs(td, imgp, stack); /* Linux sets %gs to 0, we default to _udatasel */ pcb->pcb_gs = 0; load_gs(0); pcb->pcb_initial_npxcw = __LINUX_NPXCW__; } static void linux_get_machine(const char **dst) { switch (cpu_class) { case CPUCLASS_686: *dst = "i686"; break; case CPUCLASS_586: *dst = "i586"; break; case CPUCLASS_486: *dst = "i486"; break; default: *dst = "i386"; } } struct sysentvec linux_sysvec = { .sv_size = LINUX_SYS_MAXSYSCALL, .sv_table = linux_sysent, .sv_mask = 0, .sv_errsize = ELAST + 1, .sv_errtbl = bsd_to_linux_errno, .sv_transtrap = translate_traps, .sv_fixup = linux_fixup, .sv_sendsig = linux_sendsig, .sv_sigcode = &_binary_linux_locore_o_start, .sv_szsigcode = &linux_szsigcode, .sv_name = "Linux a.out", .sv_coredump = NULL, .sv_imgact_try = exec_linux_imgact_try, .sv_minsigstksz = LINUX_MINSIGSTKSZ, .sv_pagesize = PAGE_SIZE, .sv_minuser = VM_MIN_ADDRESS, .sv_maxuser = VM_MAXUSER_ADDRESS, .sv_usrstack = LINUX_USRSTACK, .sv_psstrings = PS_STRINGS, .sv_stackprot = VM_PROT_ALL, .sv_copyout_strings = exec_copyout_strings, .sv_setregs = exec_linux_setregs, .sv_fixlimit = NULL, .sv_maxssiz = NULL, .sv_flags = SV_ABI_LINUX | SV_AOUT | SV_IA32 | SV_ILP32, .sv_set_syscall_retval = cpu_set_syscall_retval, .sv_fetch_syscall_args = linux_fetch_syscall_args, .sv_syscallnames = NULL, .sv_shared_page_base = LINUX_SHAREDPAGE, .sv_shared_page_len = PAGE_SIZE, .sv_schedtail = linux_schedtail, .sv_thread_detach = linux_thread_detach, .sv_trap = NULL, }; INIT_SYSENTVEC(aout_sysvec, &linux_sysvec); struct sysentvec elf_linux_sysvec = { .sv_size = LINUX_SYS_MAXSYSCALL, .sv_table = linux_sysent, .sv_mask = 0, .sv_errsize = ELAST + 1, .sv_errtbl = bsd_to_linux_errno, .sv_transtrap = translate_traps, .sv_fixup = elf_linux_fixup, .sv_sendsig = linux_sendsig, .sv_sigcode = &_binary_linux_locore_o_start, .sv_szsigcode = &linux_szsigcode, .sv_name = "Linux ELF", .sv_coredump = elf32_coredump, .sv_imgact_try = exec_linux_imgact_try, .sv_minsigstksz = LINUX_MINSIGSTKSZ, .sv_pagesize = PAGE_SIZE, .sv_minuser = VM_MIN_ADDRESS, .sv_maxuser = VM_MAXUSER_ADDRESS, .sv_usrstack = LINUX_USRSTACK, .sv_psstrings = LINUX_PS_STRINGS, .sv_stackprot = VM_PROT_ALL, .sv_copyout_strings = linux_copyout_strings, .sv_setregs = exec_linux_setregs, .sv_fixlimit = NULL, .sv_maxssiz = NULL, .sv_flags = SV_ABI_LINUX | SV_IA32 | SV_ILP32 | SV_SHP, .sv_set_syscall_retval = cpu_set_syscall_retval, .sv_fetch_syscall_args = linux_fetch_syscall_args, .sv_syscallnames = NULL, .sv_shared_page_base = LINUX_SHAREDPAGE, .sv_shared_page_len = PAGE_SIZE, .sv_schedtail = linux_schedtail, .sv_thread_detach = linux_thread_detach, .sv_trap = NULL, }; static void linux_vdso_install(void *param) { linux_szsigcode = (&_binary_linux_locore_o_end - &_binary_linux_locore_o_start); if (linux_szsigcode > elf_linux_sysvec.sv_shared_page_len) panic("Linux invalid vdso size\n"); __elfN(linux_vdso_fixup)(&elf_linux_sysvec); linux_shared_page_obj = __elfN(linux_shared_page_init) (&linux_shared_page_mapping); __elfN(linux_vdso_reloc)(&elf_linux_sysvec, LINUX_SHAREDPAGE); bcopy(elf_linux_sysvec.sv_sigcode, linux_shared_page_mapping, linux_szsigcode); elf_linux_sysvec.sv_shared_page_obj = linux_shared_page_obj; } SYSINIT(elf_linux_vdso_init, SI_SUB_EXEC, SI_ORDER_ANY, (sysinit_cfunc_t)linux_vdso_install, NULL); static void linux_vdso_deinstall(void *param) { __elfN(linux_shared_page_fini)(linux_shared_page_obj); }; SYSUNINIT(elf_linux_vdso_uninit, SI_SUB_EXEC, SI_ORDER_FIRST, (sysinit_cfunc_t)linux_vdso_deinstall, NULL); static char GNU_ABI_VENDOR[] = "GNU"; static int GNULINUX_ABI_DESC = 0; static boolean_t linux_trans_osrel(const Elf_Note *note, int32_t *osrel) { const Elf32_Word *desc; uintptr_t p; p = (uintptr_t)(note + 1); p += roundup2(note->n_namesz, sizeof(Elf32_Addr)); desc = (const Elf32_Word *)p; if (desc[0] != GNULINUX_ABI_DESC) return (FALSE); /* * For linux we encode osrel as follows (see linux_mib.c): * VVVMMMIII (version, major, minor), see linux_mib.c. */ *osrel = desc[1] * 1000000 + desc[2] * 1000 + desc[3]; return (TRUE); } static Elf_Brandnote linux_brandnote = { .hdr.n_namesz = sizeof(GNU_ABI_VENDOR), .hdr.n_descsz = 16, /* XXX at least 16 */ .hdr.n_type = 1, .vendor = GNU_ABI_VENDOR, .flags = BN_TRANSLATE_OSREL, .trans_osrel = linux_trans_osrel }; static Elf32_Brandinfo linux_brand = { .brand = ELFOSABI_LINUX, .machine = EM_386, .compat_3_brand = "Linux", .emul_path = "/compat/linux", .interp_path = "/lib/ld-linux.so.1", .sysvec = &elf_linux_sysvec, .interp_newpath = NULL, .brand_note = &linux_brandnote, .flags = BI_CAN_EXEC_DYN | BI_BRAND_NOTE }; static Elf32_Brandinfo linux_glibc2brand = { .brand = ELFOSABI_LINUX, .machine = EM_386, .compat_3_brand = "Linux", .emul_path = "/compat/linux", .interp_path = "/lib/ld-linux.so.2", .sysvec = &elf_linux_sysvec, .interp_newpath = NULL, .brand_note = &linux_brandnote, .flags = BI_CAN_EXEC_DYN | BI_BRAND_NOTE }; Elf32_Brandinfo *linux_brandlist[] = { &linux_brand, &linux_glibc2brand, NULL }; static int linux_elf_modevent(module_t mod, int type, void *data) { Elf32_Brandinfo **brandinfo; int error; struct linux_ioctl_handler **lihp; error = 0; switch(type) { case MOD_LOAD: for (brandinfo = &linux_brandlist[0]; *brandinfo != NULL; ++brandinfo) if (elf32_insert_brand_entry(*brandinfo) < 0) error = EINVAL; if (error == 0) { SET_FOREACH(lihp, linux_ioctl_handler_set) linux_ioctl_register_handler(*lihp); LIST_INIT(&futex_list); mtx_init(&futex_mtx, "ftllk", NULL, MTX_DEF); linux_exit_tag = EVENTHANDLER_REGISTER(process_exit, linux_proc_exit, NULL, 1000); linux_exec_tag = EVENTHANDLER_REGISTER(process_exec, linux_proc_exec, NULL, 1000); linux_thread_dtor_tag = EVENTHANDLER_REGISTER(thread_dtor, linux_thread_dtor, NULL, EVENTHANDLER_PRI_ANY); linux_get_machine(&linux_kplatform); linux_szplatform = roundup(strlen(linux_kplatform) + 1, sizeof(char *)); linux_osd_jail_register(); stclohz = (stathz ? stathz : hz); if (bootverbose) printf("Linux ELF exec handler installed\n"); } else printf("cannot insert Linux ELF brand handler\n"); break; case MOD_UNLOAD: for (brandinfo = &linux_brandlist[0]; *brandinfo != NULL; ++brandinfo) if (elf32_brand_inuse(*brandinfo)) error = EBUSY; if (error == 0) { for (brandinfo = &linux_brandlist[0]; *brandinfo != NULL; ++brandinfo) if (elf32_remove_brand_entry(*brandinfo) < 0) error = EINVAL; } if (error == 0) { SET_FOREACH(lihp, linux_ioctl_handler_set) linux_ioctl_unregister_handler(*lihp); mtx_destroy(&futex_mtx); EVENTHANDLER_DEREGISTER(process_exit, linux_exit_tag); EVENTHANDLER_DEREGISTER(process_exec, linux_exec_tag); EVENTHANDLER_DEREGISTER(thread_dtor, linux_thread_dtor_tag); linux_osd_jail_deregister(); if (bootverbose) printf("Linux ELF exec handler removed\n"); } else printf("Could not deinstall ELF interpreter entry\n"); break; default: return (EOPNOTSUPP); } return (error); } static moduledata_t linux_elf_mod = { "linuxelf", linux_elf_modevent, 0 }; DECLARE_MODULE_TIED(linuxelf, linux_elf_mod, SI_SUB_EXEC, SI_ORDER_ANY); +FEATURE(linux, "Linux 32bit support"); Index: projects/release-pkg/sys/kern/kern_racct.c =================================================================== --- projects/release-pkg/sys/kern/kern_racct.c (revision 297604) +++ projects/release-pkg/sys/kern/kern_racct.c (revision 297605) @@ -1,1271 +1,1273 @@ /*- * Copyright (c) 2010 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala 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. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include "opt_sched.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 RCTL #include #endif #ifdef RACCT FEATURE(racct, "Resource Accounting"); /* * Do not block processes that have their %cpu usage <= pcpu_threshold. */ static int pcpu_threshold = 1; #ifdef RACCT_DEFAULT_TO_DISABLED int racct_enable = 0; #else int racct_enable = 1; #endif SYSCTL_NODE(_kern, OID_AUTO, racct, CTLFLAG_RW, 0, "Resource Accounting"); SYSCTL_UINT(_kern_racct, OID_AUTO, enable, CTLFLAG_RDTUN, &racct_enable, 0, "Enable RACCT/RCTL"); SYSCTL_UINT(_kern_racct, OID_AUTO, pcpu_threshold, CTLFLAG_RW, &pcpu_threshold, 0, "Processes with higher %cpu usage than this value can be throttled."); /* * How many seconds it takes to use the scheduler %cpu calculations. When a * process starts, we compute its %cpu usage by dividing its runtime by the * process wall clock time. After RACCT_PCPU_SECS pass, we use the value * provided by the scheduler. */ #define RACCT_PCPU_SECS 3 static struct mtx racct_lock; MTX_SYSINIT(racct_lock, &racct_lock, "racct lock", MTX_DEF); +#define RACCT_LOCK() mtx_lock(&racct_lock) +#define RACCT_UNLOCK() mtx_unlock(&racct_lock) +#define RACCT_LOCK_ASSERT() mtx_assert(&racct_lock, MA_OWNED) + static uma_zone_t racct_zone; static void racct_sub_racct(struct racct *dest, const struct racct *src); static void racct_sub_cred_locked(struct ucred *cred, int resource, uint64_t amount); static void racct_add_cred_locked(struct ucred *cred, int resource, uint64_t amount); SDT_PROVIDER_DEFINE(racct); SDT_PROBE_DEFINE3(racct, , rusage, add, "struct proc *", "int", "uint64_t"); SDT_PROBE_DEFINE3(racct, , rusage, add__failure, "struct proc *", "int", "uint64_t"); SDT_PROBE_DEFINE3(racct, , rusage, add__cred, "struct ucred *", "int", "uint64_t"); SDT_PROBE_DEFINE3(racct, , rusage, add__force, "struct proc *", "int", "uint64_t"); SDT_PROBE_DEFINE3(racct, , rusage, set, "struct proc *", "int", "uint64_t"); SDT_PROBE_DEFINE3(racct, , rusage, set__failure, "struct proc *", "int", "uint64_t"); SDT_PROBE_DEFINE3(racct, , rusage, set__force, "struct proc *", "int", "uint64_t"); SDT_PROBE_DEFINE3(racct, , rusage, sub, "struct proc *", "int", "uint64_t"); SDT_PROBE_DEFINE3(racct, , rusage, sub__cred, "struct ucred *", "int", "uint64_t"); SDT_PROBE_DEFINE1(racct, , racct, create, "struct racct *"); SDT_PROBE_DEFINE1(racct, , racct, destroy, "struct racct *"); SDT_PROBE_DEFINE2(racct, , racct, join, "struct racct *", "struct racct *"); SDT_PROBE_DEFINE2(racct, , racct, join__failure, "struct racct *", "struct racct *"); SDT_PROBE_DEFINE2(racct, , racct, leave, "struct racct *", "struct racct *"); int racct_types[] = { [RACCT_CPU] = RACCT_IN_MILLIONS, [RACCT_DATA] = RACCT_RECLAIMABLE | RACCT_INHERITABLE | RACCT_DENIABLE, [RACCT_STACK] = RACCT_RECLAIMABLE | RACCT_INHERITABLE | RACCT_DENIABLE, [RACCT_CORE] = RACCT_DENIABLE, [RACCT_RSS] = RACCT_RECLAIMABLE, [RACCT_MEMLOCK] = RACCT_RECLAIMABLE | RACCT_DENIABLE, [RACCT_NPROC] = RACCT_RECLAIMABLE | RACCT_DENIABLE, [RACCT_NOFILE] = RACCT_RECLAIMABLE | RACCT_INHERITABLE | RACCT_DENIABLE, [RACCT_VMEM] = RACCT_RECLAIMABLE | RACCT_INHERITABLE | RACCT_DENIABLE, [RACCT_NPTS] = RACCT_RECLAIMABLE | RACCT_DENIABLE | RACCT_SLOPPY, [RACCT_SWAP] = RACCT_RECLAIMABLE | RACCT_DENIABLE | RACCT_SLOPPY, [RACCT_NTHR] = RACCT_RECLAIMABLE | RACCT_DENIABLE, [RACCT_MSGQQUEUED] = RACCT_RECLAIMABLE | RACCT_DENIABLE | RACCT_SLOPPY, [RACCT_MSGQSIZE] = RACCT_RECLAIMABLE | RACCT_DENIABLE | RACCT_SLOPPY, [RACCT_NMSGQ] = RACCT_RECLAIMABLE | RACCT_DENIABLE | RACCT_SLOPPY, [RACCT_NSEM] = RACCT_RECLAIMABLE | RACCT_DENIABLE | RACCT_SLOPPY, [RACCT_NSEMOP] = RACCT_RECLAIMABLE | RACCT_INHERITABLE | RACCT_DENIABLE, [RACCT_NSHM] = RACCT_RECLAIMABLE | RACCT_DENIABLE | RACCT_SLOPPY, [RACCT_SHMSIZE] = RACCT_RECLAIMABLE | RACCT_DENIABLE | RACCT_SLOPPY, [RACCT_WALLCLOCK] = RACCT_IN_MILLIONS, [RACCT_PCTCPU] = RACCT_DECAYING | RACCT_DENIABLE | RACCT_IN_MILLIONS }; static const fixpt_t RACCT_DECAY_FACTOR = 0.3 * FSCALE; #ifdef SCHED_4BSD /* * Contains intermediate values for %cpu calculations to avoid using floating * point in the kernel. * ccpu_exp[k] = FSCALE * (ccpu/FSCALE)^k = FSCALE * exp(-k/20) * It is needed only for the 4BSD scheduler, because in ULE, the ccpu equals to * zero so the calculations are more straightforward. */ fixpt_t ccpu_exp[] = { [0] = FSCALE * 1, [1] = FSCALE * 0.95122942450071400909, [2] = FSCALE * 0.90483741803595957316, [3] = FSCALE * 0.86070797642505780722, [4] = FSCALE * 0.81873075307798185866, [5] = FSCALE * 0.77880078307140486824, [6] = FSCALE * 0.74081822068171786606, [7] = FSCALE * 0.70468808971871343435, [8] = FSCALE * 0.67032004603563930074, [9] = FSCALE * 0.63762815162177329314, [10] = FSCALE * 0.60653065971263342360, [11] = FSCALE * 0.57694981038048669531, [12] = FSCALE * 0.54881163609402643262, [13] = FSCALE * 0.52204577676101604789, [14] = FSCALE * 0.49658530379140951470, [15] = FSCALE * 0.47236655274101470713, [16] = FSCALE * 0.44932896411722159143, [17] = FSCALE * 0.42741493194872666992, [18] = FSCALE * 0.40656965974059911188, [19] = FSCALE * 0.38674102345450120691, [20] = FSCALE * 0.36787944117144232159, [21] = FSCALE * 0.34993774911115535467, [22] = FSCALE * 0.33287108369807955328, [23] = FSCALE * 0.31663676937905321821, [24] = FSCALE * 0.30119421191220209664, [25] = FSCALE * 0.28650479686019010032, [26] = FSCALE * 0.27253179303401260312, [27] = FSCALE * 0.25924026064589150757, [28] = FSCALE * 0.24659696394160647693, [29] = FSCALE * 0.23457028809379765313, [30] = FSCALE * 0.22313016014842982893, [31] = FSCALE * 0.21224797382674305771, [32] = FSCALE * 0.20189651799465540848, [33] = FSCALE * 0.19204990862075411423, [34] = FSCALE * 0.18268352405273465022, [35] = FSCALE * 0.17377394345044512668, [36] = FSCALE * 0.16529888822158653829, [37] = FSCALE * 0.15723716631362761621, [38] = FSCALE * 0.14956861922263505264, [39] = FSCALE * 0.14227407158651357185, [40] = FSCALE * 0.13533528323661269189, [41] = FSCALE * 0.12873490358780421886, [42] = FSCALE * 0.12245642825298191021, [43] = FSCALE * 0.11648415777349695786, [44] = FSCALE * 0.11080315836233388333, [45] = FSCALE * 0.10539922456186433678, [46] = FSCALE * 0.10025884372280373372, [47] = FSCALE * 0.09536916221554961888, [48] = FSCALE * 0.09071795328941250337, [49] = FSCALE * 0.08629358649937051097, [50] = FSCALE * 0.08208499862389879516, [51] = FSCALE * 0.07808166600115315231, [52] = FSCALE * 0.07427357821433388042, [53] = FSCALE * 0.07065121306042958674, [54] = FSCALE * 0.06720551273974976512, [55] = FSCALE * 0.06392786120670757270, [56] = FSCALE * 0.06081006262521796499, [57] = FSCALE * 0.05784432087483846296, [58] = FSCALE * 0.05502322005640722902, [59] = FSCALE * 0.05233970594843239308, [60] = FSCALE * 0.04978706836786394297, [61] = FSCALE * 0.04735892439114092119, [62] = FSCALE * 0.04504920239355780606, [63] = FSCALE * 0.04285212686704017991, [64] = FSCALE * 0.04076220397836621516, [65] = FSCALE * 0.03877420783172200988, [66] = FSCALE * 0.03688316740124000544, [67] = FSCALE * 0.03508435410084502588, [68] = FSCALE * 0.03337326996032607948, [69] = FSCALE * 0.03174563637806794323, [70] = FSCALE * 0.03019738342231850073, [71] = FSCALE * 0.02872463965423942912, [72] = FSCALE * 0.02732372244729256080, [73] = FSCALE * 0.02599112877875534358, [74] = FSCALE * 0.02472352647033939120, [75] = FSCALE * 0.02351774585600910823, [76] = FSCALE * 0.02237077185616559577, [77] = FSCALE * 0.02127973643837716938, [78] = FSCALE * 0.02024191144580438847, [79] = FSCALE * 0.01925470177538692429, [80] = FSCALE * 0.01831563888873418029, [81] = FSCALE * 0.01742237463949351138, [82] = FSCALE * 0.01657267540176124754, [83] = FSCALE * 0.01576441648485449082, [84] = FSCALE * 0.01499557682047770621, [85] = FSCALE * 0.01426423390899925527, [86] = FSCALE * 0.01356855901220093175, [87] = FSCALE * 0.01290681258047986886, [88] = FSCALE * 0.01227733990306844117, [89] = FSCALE * 0.01167856697039544521, [90] = FSCALE * 0.01110899653824230649, [91] = FSCALE * 0.01056720438385265337, [92] = FSCALE * 0.01005183574463358164, [93] = FSCALE * 0.00956160193054350793, [94] = FSCALE * 0.00909527710169581709, [95] = FSCALE * 0.00865169520312063417, [96] = FSCALE * 0.00822974704902002884, [97] = FSCALE * 0.00782837754922577143, [98] = FSCALE * 0.00744658307092434051, [99] = FSCALE * 0.00708340892905212004, [100] = FSCALE * 0.00673794699908546709, [101] = FSCALE * 0.00640933344625638184, [102] = FSCALE * 0.00609674656551563610, [103] = FSCALE * 0.00579940472684214321, [104] = FSCALE * 0.00551656442076077241, [105] = FSCALE * 0.00524751839918138427, [106] = FSCALE * 0.00499159390691021621, [107] = FSCALE * 0.00474815099941147558, [108] = FSCALE * 0.00451658094261266798, [109] = FSCALE * 0.00429630469075234057, [110] = FSCALE * 0.00408677143846406699, }; #endif #define CCPU_EXP_MAX 110 /* * This function is analogical to the getpcpu() function in the ps(1) command. * They should both calculate in the same way so that the racct %cpu * calculations are consistent with the values showed by the ps(1) tool. * The calculations are more complex in the 4BSD scheduler because of the value * of the ccpu variable. In ULE it is defined to be zero which saves us some * work. */ static uint64_t racct_getpcpu(struct proc *p, u_int pcpu) { u_int swtime; #ifdef SCHED_4BSD fixpt_t pctcpu, pctcpu_next; #endif #ifdef SMP struct pcpu *pc; int found; #endif fixpt_t p_pctcpu; struct thread *td; ASSERT_RACCT_ENABLED(); /* * If the process is swapped out, we count its %cpu usage as zero. * This behaviour is consistent with the userland ps(1) tool. */ if ((p->p_flag & P_INMEM) == 0) return (0); swtime = (ticks - p->p_swtick) / hz; /* * For short-lived processes, the sched_pctcpu() returns small * values even for cpu intensive processes. Therefore we use * our own estimate in this case. */ if (swtime < RACCT_PCPU_SECS) return (pcpu); p_pctcpu = 0; FOREACH_THREAD_IN_PROC(p, td) { if (td == PCPU_GET(idlethread)) continue; #ifdef SMP found = 0; STAILQ_FOREACH(pc, &cpuhead, pc_allcpu) { if (td == pc->pc_idlethread) { found = 1; break; } } if (found) continue; #endif thread_lock(td); #ifdef SCHED_4BSD pctcpu = sched_pctcpu(td); /* Count also the yet unfinished second. */ pctcpu_next = (pctcpu * ccpu_exp[1]) >> FSHIFT; pctcpu_next += sched_pctcpu_delta(td); p_pctcpu += max(pctcpu, pctcpu_next); #else /* * In ULE the %cpu statistics are updated on every * sched_pctcpu() call. So special calculations to * account for the latest (unfinished) second are * not needed. */ p_pctcpu += sched_pctcpu(td); #endif thread_unlock(td); } #ifdef SCHED_4BSD if (swtime <= CCPU_EXP_MAX) return ((100 * (uint64_t)p_pctcpu * 1000000) / (FSCALE - ccpu_exp[swtime])); #endif return ((100 * (uint64_t)p_pctcpu * 1000000) / FSCALE); } static void racct_add_racct(struct racct *dest, const struct racct *src) { int i; ASSERT_RACCT_ENABLED(); - mtx_assert(&racct_lock, MA_OWNED); + RACCT_LOCK_ASSERT(); /* * Update resource usage in dest. */ for (i = 0; i <= RACCT_MAX; i++) { KASSERT(dest->r_resources[i] >= 0, ("%s: resource %d propagation meltdown: dest < 0", __func__, i)); KASSERT(src->r_resources[i] >= 0, ("%s: resource %d propagation meltdown: src < 0", __func__, i)); dest->r_resources[i] += src->r_resources[i]; } } static void racct_sub_racct(struct racct *dest, const struct racct *src) { int i; ASSERT_RACCT_ENABLED(); - mtx_assert(&racct_lock, MA_OWNED); + RACCT_LOCK_ASSERT(); /* * Update resource usage in dest. */ for (i = 0; i <= RACCT_MAX; i++) { if (!RACCT_IS_SLOPPY(i) && !RACCT_IS_DECAYING(i)) { KASSERT(dest->r_resources[i] >= 0, ("%s: resource %d propagation meltdown: dest < 0", __func__, i)); KASSERT(src->r_resources[i] >= 0, ("%s: resource %d propagation meltdown: src < 0", __func__, i)); KASSERT(src->r_resources[i] <= dest->r_resources[i], ("%s: resource %d propagation meltdown: src > dest", __func__, i)); } if (RACCT_CAN_DROP(i)) { dest->r_resources[i] -= src->r_resources[i]; if (dest->r_resources[i] < 0) { KASSERT(RACCT_IS_SLOPPY(i) || RACCT_IS_DECAYING(i), ("%s: resource %d usage < 0", __func__, i)); dest->r_resources[i] = 0; } } } } void racct_create(struct racct **racctp) { if (!racct_enable) return; SDT_PROBE1(racct, , racct, create, racctp); KASSERT(*racctp == NULL, ("racct already allocated")); *racctp = uma_zalloc(racct_zone, M_WAITOK | M_ZERO); } static void racct_destroy_locked(struct racct **racctp) { int i; struct racct *racct; ASSERT_RACCT_ENABLED(); SDT_PROBE1(racct, , racct, destroy, racctp); - mtx_assert(&racct_lock, MA_OWNED); + RACCT_LOCK_ASSERT(); KASSERT(racctp != NULL, ("NULL racctp")); KASSERT(*racctp != NULL, ("NULL racct")); racct = *racctp; for (i = 0; i <= RACCT_MAX; i++) { if (RACCT_IS_SLOPPY(i)) continue; if (!RACCT_IS_RECLAIMABLE(i)) continue; KASSERT(racct->r_resources[i] == 0, ("destroying non-empty racct: " "%ju allocated for resource %d\n", racct->r_resources[i], i)); } uma_zfree(racct_zone, racct); *racctp = NULL; } void racct_destroy(struct racct **racct) { if (!racct_enable) return; - mtx_lock(&racct_lock); + RACCT_LOCK(); racct_destroy_locked(racct); - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); } /* * Increase consumption of 'resource' by 'amount' for 'racct', * but not its parents. Differently from other cases, 'amount' here * may be less than zero. */ static void racct_adjust_resource(struct racct *racct, int resource, int64_t amount) { ASSERT_RACCT_ENABLED(); - mtx_assert(&racct_lock, MA_OWNED); + RACCT_LOCK_ASSERT(); KASSERT(racct != NULL, ("NULL racct")); racct->r_resources[resource] += amount; if (racct->r_resources[resource] < 0) { KASSERT(RACCT_IS_SLOPPY(resource) || RACCT_IS_DECAYING(resource), ("%s: resource %d usage < 0", __func__, resource)); racct->r_resources[resource] = 0; } /* * There are some cases where the racct %cpu resource would grow * beyond 100% per core. For example in racct_proc_exit() we add * the process %cpu usage to the ucred racct containers. If too * many processes terminated in a short time span, the ucred %cpu * resource could grow too much. Also, the 4BSD scheduler sometimes * returns for a thread more than 100% cpu usage. So we set a sane * boundary here to 100% * the maxumum number of CPUs. */ if ((resource == RACCT_PCTCPU) && (racct->r_resources[RACCT_PCTCPU] > 100 * 1000000 * (int64_t)MAXCPU)) racct->r_resources[RACCT_PCTCPU] = 100 * 1000000 * (int64_t)MAXCPU; } static int racct_add_locked(struct proc *p, int resource, uint64_t amount, int force) { #ifdef RCTL int error; #endif ASSERT_RACCT_ENABLED(); /* * We need proc lock to dereference p->p_ucred. */ PROC_LOCK_ASSERT(p, MA_OWNED); #ifdef RCTL error = rctl_enforce(p, resource, amount); if (error && !force && RACCT_IS_DENIABLE(resource)) { SDT_PROBE3(racct, , rusage, add__failure, p, resource, amount); return (error); } #endif racct_adjust_resource(p->p_racct, resource, amount); racct_add_cred_locked(p->p_ucred, resource, amount); return (0); } /* * Increase allocation of 'resource' by 'amount' for process 'p'. * Return 0 if it's below limits, or errno, if it's not. */ int racct_add(struct proc *p, int resource, uint64_t amount) { int error; if (!racct_enable) return (0); SDT_PROBE3(racct, , rusage, add, p, resource, amount); - mtx_lock(&racct_lock); + RACCT_LOCK(); error = racct_add_locked(p, resource, amount, 0); - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); return (error); } /* * Increase allocation of 'resource' by 'amount' for process 'p'. * Doesn't check for limits and never fails. */ void racct_add_force(struct proc *p, int resource, uint64_t amount) { if (!racct_enable) return; SDT_PROBE3(racct, , rusage, add__force, p, resource, amount); - mtx_lock(&racct_lock); + RACCT_LOCK(); racct_add_locked(p, resource, amount, 1); - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); } static void racct_add_cred_locked(struct ucred *cred, int resource, uint64_t amount) { struct prison *pr; ASSERT_RACCT_ENABLED(); SDT_PROBE3(racct, , rusage, add__cred, cred, resource, amount); racct_adjust_resource(cred->cr_ruidinfo->ui_racct, resource, amount); for (pr = cred->cr_prison; pr != NULL; pr = pr->pr_parent) racct_adjust_resource(pr->pr_prison_racct->prr_racct, resource, amount); racct_adjust_resource(cred->cr_loginclass->lc_racct, resource, amount); } /* * Increase allocation of 'resource' by 'amount' for credential 'cred'. * Doesn't check for limits and never fails. */ void racct_add_cred(struct ucred *cred, int resource, uint64_t amount) { if (!racct_enable) return; - mtx_lock(&racct_lock); + RACCT_LOCK(); racct_add_cred_locked(cred, resource, amount); - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); } static int racct_set_locked(struct proc *p, int resource, uint64_t amount, int force) { int64_t old_amount, decayed_amount; int64_t diff_proc, diff_cred; #ifdef RCTL int error; #endif ASSERT_RACCT_ENABLED(); /* * We need proc lock to dereference p->p_ucred. */ PROC_LOCK_ASSERT(p, MA_OWNED); old_amount = p->p_racct->r_resources[resource]; /* * The diffs may be negative. */ diff_proc = amount - old_amount; if (RACCT_IS_DECAYING(resource)) { /* * Resources in per-credential racct containers may decay. * If this is the case, we need to calculate the difference * between the new amount and the proportional value of the * old amount that has decayed in the ucred racct containers. */ decayed_amount = old_amount * RACCT_DECAY_FACTOR / FSCALE; diff_cred = amount - decayed_amount; } else diff_cred = diff_proc; #ifdef notyet KASSERT(diff_proc >= 0 || RACCT_CAN_DROP(resource), ("%s: usage of non-droppable resource %d dropping", __func__, resource)); #endif #ifdef RCTL if (diff_proc > 0) { error = rctl_enforce(p, resource, diff_proc); if (error && !force && RACCT_IS_DENIABLE(resource)) { SDT_PROBE3(racct, , rusage, set__failure, p, resource, amount); return (error); } } #endif racct_adjust_resource(p->p_racct, resource, diff_proc); if (diff_cred > 0) racct_add_cred_locked(p->p_ucred, resource, diff_cred); else if (diff_cred < 0) racct_sub_cred_locked(p->p_ucred, resource, -diff_cred); return (0); } /* * Set allocation of 'resource' to 'amount' for process 'p'. * Return 0 if it's below limits, or errno, if it's not. * * Note that decreasing the allocation always returns 0, * even if it's above the limit. */ int racct_set(struct proc *p, int resource, uint64_t amount) { int error; if (!racct_enable) return (0); SDT_PROBE3(racct, , rusage, set__force, p, resource, amount); - mtx_lock(&racct_lock); + RACCT_LOCK(); error = racct_set_locked(p, resource, amount, 0); - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); return (error); } void racct_set_force(struct proc *p, int resource, uint64_t amount) { if (!racct_enable) return; SDT_PROBE3(racct, , rusage, set, p, resource, amount); - mtx_lock(&racct_lock); + RACCT_LOCK(); racct_set_locked(p, resource, amount, 1); - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); } /* * Returns amount of 'resource' the process 'p' can keep allocated. * Allocating more than that would be denied, unless the resource * is marked undeniable. Amount of already allocated resource does * not matter. */ uint64_t racct_get_limit(struct proc *p, int resource) { if (!racct_enable) return (UINT64_MAX); #ifdef RCTL return (rctl_get_limit(p, resource)); #else return (UINT64_MAX); #endif } /* * Returns amount of 'resource' the process 'p' can keep allocated. * Allocating more than that would be denied, unless the resource * is marked undeniable. Amount of already allocated resource does * matter. */ uint64_t racct_get_available(struct proc *p, int resource) { if (!racct_enable) return (UINT64_MAX); #ifdef RCTL return (rctl_get_available(p, resource)); #else return (UINT64_MAX); #endif } /* * Returns amount of the %cpu resource that process 'p' can add to its %cpu * utilization. Adding more than that would lead to the process being * throttled. */ static int64_t racct_pcpu_available(struct proc *p) { ASSERT_RACCT_ENABLED(); #ifdef RCTL return (rctl_pcpu_available(p)); #else return (INT64_MAX); #endif } /* * Decrease allocation of 'resource' by 'amount' for process 'p'. */ void racct_sub(struct proc *p, int resource, uint64_t amount) { if (!racct_enable) return; SDT_PROBE3(racct, , rusage, sub, p, resource, amount); /* * We need proc lock to dereference p->p_ucred. */ PROC_LOCK_ASSERT(p, MA_OWNED); KASSERT(RACCT_CAN_DROP(resource), ("%s: called for non-droppable resource %d", __func__, resource)); - mtx_lock(&racct_lock); + RACCT_LOCK(); KASSERT(amount <= p->p_racct->r_resources[resource], ("%s: freeing %ju of resource %d, which is more " "than allocated %jd for %s (pid %d)", __func__, amount, resource, (intmax_t)p->p_racct->r_resources[resource], p->p_comm, p->p_pid)); racct_adjust_resource(p->p_racct, resource, -amount); racct_sub_cred_locked(p->p_ucred, resource, amount); - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); } static void racct_sub_cred_locked(struct ucred *cred, int resource, uint64_t amount) { struct prison *pr; ASSERT_RACCT_ENABLED(); SDT_PROBE3(racct, , rusage, sub__cred, cred, resource, amount); #ifdef notyet KASSERT(RACCT_CAN_DROP(resource), ("%s: called for resource %d which can not drop", __func__, resource)); #endif racct_adjust_resource(cred->cr_ruidinfo->ui_racct, resource, -amount); for (pr = cred->cr_prison; pr != NULL; pr = pr->pr_parent) racct_adjust_resource(pr->pr_prison_racct->prr_racct, resource, -amount); racct_adjust_resource(cred->cr_loginclass->lc_racct, resource, -amount); } /* * Decrease allocation of 'resource' by 'amount' for credential 'cred'. */ void racct_sub_cred(struct ucred *cred, int resource, uint64_t amount) { if (!racct_enable) return; - mtx_lock(&racct_lock); + RACCT_LOCK(); racct_sub_cred_locked(cred, resource, amount); - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); } /* * Inherit resource usage information from the parent process. */ int racct_proc_fork(struct proc *parent, struct proc *child) { int i, error = 0; if (!racct_enable) return (0); /* * Create racct for the child process. */ racct_create(&child->p_racct); PROC_LOCK(parent); PROC_LOCK(child); - mtx_lock(&racct_lock); + RACCT_LOCK(); #ifdef RCTL error = rctl_proc_fork(parent, child); if (error != 0) goto out; #endif /* Init process cpu time. */ child->p_prev_runtime = 0; child->p_throttled = 0; /* * Inherit resource usage. */ for (i = 0; i <= RACCT_MAX; i++) { if (parent->p_racct->r_resources[i] == 0 || !RACCT_IS_INHERITABLE(i)) continue; error = racct_set_locked(child, i, parent->p_racct->r_resources[i], 0); if (error != 0) goto out; } error = racct_add_locked(child, RACCT_NPROC, 1, 0); error += racct_add_locked(child, RACCT_NTHR, 1, 0); out: - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); PROC_UNLOCK(child); PROC_UNLOCK(parent); if (error != 0) racct_proc_exit(child); return (error); } /* * Called at the end of fork1(), to handle rules that require the process * to be fully initialized. */ void racct_proc_fork_done(struct proc *child) { PROC_LOCK_ASSERT(child, MA_OWNED); #ifdef RCTL if (!racct_enable) return; - mtx_lock(&racct_lock); + RACCT_LOCK(); rctl_enforce(child, RACCT_NPROC, 0); rctl_enforce(child, RACCT_NTHR, 0); - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); #endif } void racct_proc_exit(struct proc *p) { int i; uint64_t runtime; struct timeval wallclock; uint64_t pct_estimate, pct; if (!racct_enable) return; PROC_LOCK(p); /* * We don't need to calculate rux, proc_reap() has already done this. */ runtime = cputick2usec(p->p_rux.rux_runtime); #ifdef notyet KASSERT(runtime >= p->p_prev_runtime, ("runtime < p_prev_runtime")); #else if (runtime < p->p_prev_runtime) runtime = p->p_prev_runtime; #endif microuptime(&wallclock); timevalsub(&wallclock, &p->p_stats->p_start); if (wallclock.tv_sec > 0 || wallclock.tv_usec > 0) { pct_estimate = (1000000 * runtime * 100) / ((uint64_t)wallclock.tv_sec * 1000000 + wallclock.tv_usec); } else pct_estimate = 0; pct = racct_getpcpu(p, pct_estimate); - mtx_lock(&racct_lock); + RACCT_LOCK(); racct_set_locked(p, RACCT_CPU, runtime, 0); racct_add_cred_locked(p->p_ucred, RACCT_PCTCPU, pct); for (i = 0; i <= RACCT_MAX; i++) { if (p->p_racct->r_resources[i] == 0) continue; if (!RACCT_IS_RECLAIMABLE(i)) continue; racct_set_locked(p, i, 0, 0); } - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); PROC_UNLOCK(p); #ifdef RCTL rctl_racct_release(p->p_racct); #endif racct_destroy(&p->p_racct); } /* * Called after credentials change, to move resource utilisation * between raccts. */ void racct_proc_ucred_changed(struct proc *p, struct ucred *oldcred, struct ucred *newcred) { struct uidinfo *olduip, *newuip; struct loginclass *oldlc, *newlc; struct prison *oldpr, *newpr, *pr; if (!racct_enable) return; PROC_LOCK_ASSERT(p, MA_NOTOWNED); newuip = newcred->cr_ruidinfo; olduip = oldcred->cr_ruidinfo; newlc = newcred->cr_loginclass; oldlc = oldcred->cr_loginclass; newpr = newcred->cr_prison; oldpr = oldcred->cr_prison; - mtx_lock(&racct_lock); + RACCT_LOCK(); if (newuip != olduip) { racct_sub_racct(olduip->ui_racct, p->p_racct); racct_add_racct(newuip->ui_racct, p->p_racct); } if (newlc != oldlc) { racct_sub_racct(oldlc->lc_racct, p->p_racct); racct_add_racct(newlc->lc_racct, p->p_racct); } if (newpr != oldpr) { for (pr = oldpr; pr != NULL; pr = pr->pr_parent) racct_sub_racct(pr->pr_prison_racct->prr_racct, p->p_racct); for (pr = newpr; pr != NULL; pr = pr->pr_parent) racct_add_racct(pr->pr_prison_racct->prr_racct, p->p_racct); } - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); #ifdef RCTL rctl_proc_ucred_changed(p, newcred); #endif } void racct_move(struct racct *dest, struct racct *src) { ASSERT_RACCT_ENABLED(); - mtx_lock(&racct_lock); - + RACCT_LOCK(); racct_add_racct(dest, src); racct_sub_racct(src, src); - - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); } static void racct_proc_throttle(struct proc *p) { struct thread *td; #ifdef SMP int cpuid; #endif ASSERT_RACCT_ENABLED(); PROC_LOCK_ASSERT(p, MA_OWNED); /* * Do not block kernel processes. Also do not block processes with * low %cpu utilization to improve interactivity. */ if (((p->p_flag & (P_SYSTEM | P_KPROC)) != 0) || (p->p_racct->r_resources[RACCT_PCTCPU] <= pcpu_threshold)) return; p->p_throttled = 1; FOREACH_THREAD_IN_PROC(p, td) { thread_lock(td); switch (td->td_state) { case TDS_RUNQ: /* * If the thread is on the scheduler run-queue, we can * not just remove it from there. So we set the flag * TDF_NEEDRESCHED for the thread, so that once it is * running, it is taken off the cpu as soon as possible. */ td->td_flags |= TDF_NEEDRESCHED; break; case TDS_RUNNING: /* * If the thread is running, we request a context * switch for it by setting the TDF_NEEDRESCHED flag. */ td->td_flags |= TDF_NEEDRESCHED; #ifdef SMP cpuid = td->td_oncpu; if ((cpuid != NOCPU) && (td != curthread)) ipi_cpu(cpuid, IPI_AST); #endif break; default: break; } thread_unlock(td); } } static void racct_proc_wakeup(struct proc *p) { ASSERT_RACCT_ENABLED(); PROC_LOCK_ASSERT(p, MA_OWNED); if (p->p_throttled) { p->p_throttled = 0; wakeup(p->p_racct); } } static void racct_decay_callback(struct racct *racct, void *dummy1, void *dummy2) { int64_t r_old, r_new; ASSERT_RACCT_ENABLED(); - mtx_assert(&racct_lock, MA_OWNED); + RACCT_LOCK_ASSERT(); r_old = racct->r_resources[RACCT_PCTCPU]; /* If there is nothing to decay, just exit. */ if (r_old <= 0) return; r_new = r_old * RACCT_DECAY_FACTOR / FSCALE; racct->r_resources[RACCT_PCTCPU] = r_new; } static void racct_decay_pre(void) { - mtx_lock(&racct_lock); + RACCT_LOCK(); } static void racct_decay_post(void) { - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); } static void racct_decay(void) { ASSERT_RACCT_ENABLED(); ui_racct_foreach(racct_decay_callback, racct_decay_pre, racct_decay_post, NULL, NULL); loginclass_racct_foreach(racct_decay_callback, racct_decay_pre, racct_decay_post, NULL, NULL); prison_racct_foreach(racct_decay_callback, racct_decay_pre, racct_decay_post, NULL, NULL); } static void racctd(void) { struct thread *td; struct proc *p; struct timeval wallclock; uint64_t runtime; uint64_t pct, pct_estimate; ASSERT_RACCT_ENABLED(); for (;;) { racct_decay(); sx_slock(&allproc_lock); LIST_FOREACH(p, &zombproc, p_list) { PROC_LOCK(p); racct_set(p, RACCT_PCTCPU, 0); PROC_UNLOCK(p); } FOREACH_PROC_IN_SYSTEM(p) { PROC_LOCK(p); if (p->p_state != PRS_NORMAL) { PROC_UNLOCK(p); continue; } microuptime(&wallclock); timevalsub(&wallclock, &p->p_stats->p_start); PROC_STATLOCK(p); FOREACH_THREAD_IN_PROC(p, td) ruxagg(p, td); runtime = cputick2usec(p->p_rux.rux_runtime); PROC_STATUNLOCK(p); #ifdef notyet KASSERT(runtime >= p->p_prev_runtime, ("runtime < p_prev_runtime")); #else if (runtime < p->p_prev_runtime) runtime = p->p_prev_runtime; #endif p->p_prev_runtime = runtime; if (wallclock.tv_sec > 0 || wallclock.tv_usec > 0) { pct_estimate = (1000000 * runtime * 100) / ((uint64_t)wallclock.tv_sec * 1000000 + wallclock.tv_usec); } else pct_estimate = 0; pct = racct_getpcpu(p, pct_estimate); - mtx_lock(&racct_lock); + RACCT_LOCK(); racct_set_locked(p, RACCT_PCTCPU, pct, 1); racct_set_locked(p, RACCT_CPU, runtime, 0); racct_set_locked(p, RACCT_WALLCLOCK, (uint64_t)wallclock.tv_sec * 1000000 + wallclock.tv_usec, 0); - mtx_unlock(&racct_lock); + RACCT_UNLOCK(); PROC_UNLOCK(p); } /* * To ensure that processes are throttled in a fair way, we need * to iterate over all processes again and check the limits * for %cpu resource only after ucred racct containers have been * properly filled. */ FOREACH_PROC_IN_SYSTEM(p) { PROC_LOCK(p); if (p->p_state != PRS_NORMAL) { PROC_UNLOCK(p); continue; } if (racct_pcpu_available(p) <= 0) racct_proc_throttle(p); else if (p->p_throttled) racct_proc_wakeup(p); PROC_UNLOCK(p); } sx_sunlock(&allproc_lock); pause("-", hz); } } static struct kproc_desc racctd_kp = { "racctd", racctd, NULL }; static void racctd_init(void) { if (!racct_enable) return; kproc_start(&racctd_kp); } SYSINIT(racctd, SI_SUB_RACCTD, SI_ORDER_FIRST, racctd_init, NULL); static void racct_init(void) { if (!racct_enable) return; racct_zone = uma_zcreate("racct", sizeof(struct racct), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE); /* * XXX: Move this somewhere. */ prison0.pr_prison_racct = prison_racct_find("0"); } SYSINIT(racct, SI_SUB_RACCT, SI_ORDER_FIRST, racct_init, NULL); #endif /* !RACCT */ Index: projects/release-pkg/sys/kern/kern_rctl.c =================================================================== --- projects/release-pkg/sys/kern/kern_rctl.c (revision 297604) +++ projects/release-pkg/sys/kern/kern_rctl.c (revision 297605) @@ -1,1996 +1,2002 @@ /*- * Copyright (c) 2010 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala 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. * * $FreeBSD$ */ #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 #ifdef RCTL #ifndef RACCT #error "The RCTL option requires the RACCT option" #endif FEATURE(rctl, "Resource Limits"); #define HRF_DEFAULT 0 #define HRF_DONT_INHERIT 1 #define HRF_DONT_ACCUMULATE 2 #define RCTL_MAX_INBUFSIZE 4 * 1024 #define RCTL_MAX_OUTBUFSIZE 16 * 1024 * 1024 #define RCTL_LOG_BUFSIZE 128 #define RCTL_PCPU_SHIFT (10 * 1000000) unsigned int rctl_maxbufsize = RCTL_MAX_OUTBUFSIZE; static int rctl_log_rate_limit = 10; static int rctl_devctl_rate_limit = 10; SYSCTL_NODE(_kern_racct, OID_AUTO, rctl, CTLFLAG_RW, 0, "Resource Limits"); SYSCTL_UINT(_kern_racct_rctl, OID_AUTO, maxbufsize, CTLFLAG_RWTUN, &rctl_maxbufsize, 0, "Maximum output buffer size"); SYSCTL_UINT(_kern_racct_rctl, OID_AUTO, log_rate_limit, CTLFLAG_RW, &rctl_log_rate_limit, 0, "Maximum number of log messages per second"); SYSCTL_UINT(_kern_racct_rctl, OID_AUTO, devctl_rate_limit, CTLFLAG_RW, &rctl_devctl_rate_limit, 0, "Maximum number of devctl messages per second"); /* * 'rctl_rule_link' connects a rule with every racct it's related to. * For example, rule 'user:X:openfiles:deny=N/process' is linked * with uidinfo for user X, and to each process of that user. */ struct rctl_rule_link { LIST_ENTRY(rctl_rule_link) rrl_next; struct rctl_rule *rrl_rule; int rrl_exceeded; }; struct dict { const char *d_name; int d_value; }; static struct dict subjectnames[] = { { "process", RCTL_SUBJECT_TYPE_PROCESS }, { "user", RCTL_SUBJECT_TYPE_USER }, { "loginclass", RCTL_SUBJECT_TYPE_LOGINCLASS }, { "jail", RCTL_SUBJECT_TYPE_JAIL }, { NULL, -1 }}; static struct dict resourcenames[] = { { "cputime", RACCT_CPU }, { "datasize", RACCT_DATA }, { "stacksize", RACCT_STACK }, { "coredumpsize", RACCT_CORE }, { "memoryuse", RACCT_RSS }, { "memorylocked", RACCT_MEMLOCK }, { "maxproc", RACCT_NPROC }, { "openfiles", RACCT_NOFILE }, { "vmemoryuse", RACCT_VMEM }, { "pseudoterminals", RACCT_NPTS }, { "swapuse", RACCT_SWAP }, { "nthr", RACCT_NTHR }, { "msgqqueued", RACCT_MSGQQUEUED }, { "msgqsize", RACCT_MSGQSIZE }, { "nmsgq", RACCT_NMSGQ }, { "nsem", RACCT_NSEM }, { "nsemop", RACCT_NSEMOP }, { "nshm", RACCT_NSHM }, { "shmsize", RACCT_SHMSIZE }, { "wallclock", RACCT_WALLCLOCK }, { "pcpu", RACCT_PCTCPU }, { NULL, -1 }}; static struct dict actionnames[] = { { "sighup", RCTL_ACTION_SIGHUP }, { "sigint", RCTL_ACTION_SIGINT }, { "sigquit", RCTL_ACTION_SIGQUIT }, { "sigill", RCTL_ACTION_SIGILL }, { "sigtrap", RCTL_ACTION_SIGTRAP }, { "sigabrt", RCTL_ACTION_SIGABRT }, { "sigemt", RCTL_ACTION_SIGEMT }, { "sigfpe", RCTL_ACTION_SIGFPE }, { "sigkill", RCTL_ACTION_SIGKILL }, { "sigbus", RCTL_ACTION_SIGBUS }, { "sigsegv", RCTL_ACTION_SIGSEGV }, { "sigsys", RCTL_ACTION_SIGSYS }, { "sigpipe", RCTL_ACTION_SIGPIPE }, { "sigalrm", RCTL_ACTION_SIGALRM }, { "sigterm", RCTL_ACTION_SIGTERM }, { "sigurg", RCTL_ACTION_SIGURG }, { "sigstop", RCTL_ACTION_SIGSTOP }, { "sigtstp", RCTL_ACTION_SIGTSTP }, { "sigchld", RCTL_ACTION_SIGCHLD }, { "sigttin", RCTL_ACTION_SIGTTIN }, { "sigttou", RCTL_ACTION_SIGTTOU }, { "sigio", RCTL_ACTION_SIGIO }, { "sigxcpu", RCTL_ACTION_SIGXCPU }, { "sigxfsz", RCTL_ACTION_SIGXFSZ }, { "sigvtalrm", RCTL_ACTION_SIGVTALRM }, { "sigprof", RCTL_ACTION_SIGPROF }, { "sigwinch", RCTL_ACTION_SIGWINCH }, { "siginfo", RCTL_ACTION_SIGINFO }, { "sigusr1", RCTL_ACTION_SIGUSR1 }, { "sigusr2", RCTL_ACTION_SIGUSR2 }, { "sigthr", RCTL_ACTION_SIGTHR }, { "deny", RCTL_ACTION_DENY }, { "log", RCTL_ACTION_LOG }, { "devctl", RCTL_ACTION_DEVCTL }, { NULL, -1 }}; static void rctl_init(void); SYSINIT(rctl, SI_SUB_RACCT, SI_ORDER_FIRST, rctl_init, NULL); static uma_zone_t rctl_rule_link_zone; static uma_zone_t rctl_rule_zone; static struct rwlock rctl_lock; RW_SYSINIT(rctl_lock, &rctl_lock, "RCTL lock"); +#define RCTL_RLOCK() rw_rlock(&rctl_lock) +#define RCTL_RUNLOCK() rw_runlock(&rctl_lock) +#define RCTL_WLOCK() rw_wlock(&rctl_lock) +#define RCTL_WUNLOCK() rw_wunlock(&rctl_lock) +#define RCTL_LOCK_ASSERT() rw_assert(&rctl_lock, RA_LOCKED) +#define RCTL_WLOCK_ASSERT() rw_assert(&rctl_lock, RA_WLOCKED) + static int rctl_rule_fully_specified(const struct rctl_rule *rule); static void rctl_rule_to_sbuf(struct sbuf *sb, const struct rctl_rule *rule); static MALLOC_DEFINE(M_RCTL, "rctl", "Resource Limits"); static const char * rctl_subject_type_name(int subject) { int i; for (i = 0; subjectnames[i].d_name != NULL; i++) { if (subjectnames[i].d_value == subject) return (subjectnames[i].d_name); } panic("rctl_subject_type_name: unknown subject type %d", subject); } static const char * rctl_action_name(int action) { int i; for (i = 0; actionnames[i].d_name != NULL; i++) { if (actionnames[i].d_value == action) return (actionnames[i].d_name); } panic("rctl_action_name: unknown action %d", action); } const char * rctl_resource_name(int resource) { int i; for (i = 0; resourcenames[i].d_name != NULL; i++) { if (resourcenames[i].d_value == resource) return (resourcenames[i].d_name); } panic("rctl_resource_name: unknown resource %d", resource); } static struct racct * rctl_proc_rule_to_racct(const struct proc *p, const struct rctl_rule *rule) { struct ucred *cred = p->p_ucred; ASSERT_RACCT_ENABLED(); - rw_assert(&rctl_lock, RA_LOCKED); + RCTL_LOCK_ASSERT(); switch (rule->rr_per) { case RCTL_SUBJECT_TYPE_PROCESS: return (p->p_racct); case RCTL_SUBJECT_TYPE_USER: return (cred->cr_ruidinfo->ui_racct); case RCTL_SUBJECT_TYPE_LOGINCLASS: return (cred->cr_loginclass->lc_racct); case RCTL_SUBJECT_TYPE_JAIL: return (cred->cr_prison->pr_prison_racct->prr_racct); default: panic("%s: unknown per %d", __func__, rule->rr_per); } } /* * Return the amount of resource that can be allocated by 'p' before * hitting 'rule'. */ static int64_t rctl_available_resource(const struct proc *p, const struct rctl_rule *rule) { int64_t available; const struct racct *racct; ASSERT_RACCT_ENABLED(); - rw_assert(&rctl_lock, RA_LOCKED); + RCTL_LOCK_ASSERT(); racct = rctl_proc_rule_to_racct(p, rule); available = rule->rr_amount - racct->r_resources[rule->rr_resource]; return (available); } /* * Return non-zero if allocating 'amount' by proc 'p' would exceed * resource limit specified by 'rule'. */ static int rctl_would_exceed(const struct proc *p, const struct rctl_rule *rule, int64_t amount) { int64_t available; ASSERT_RACCT_ENABLED(); + RCTL_LOCK_ASSERT(); - rw_assert(&rctl_lock, RA_LOCKED); - available = rctl_available_resource(p, rule); if (available >= amount) return (0); return (1); } /* * Special version of rctl_get_available() for the %CPU resource. * We slightly cheat here and return less than we normally would. */ int64_t rctl_pcpu_available(const struct proc *p) { struct rctl_rule *rule; struct rctl_rule_link *link; int64_t available, minavailable, limit; ASSERT_RACCT_ENABLED(); minavailable = INT64_MAX; limit = 0; - rw_rlock(&rctl_lock); + RCTL_RLOCK(); LIST_FOREACH(link, &p->p_racct->r_rule_links, rrl_next) { rule = link->rrl_rule; if (rule->rr_resource != RACCT_PCTCPU) continue; if (rule->rr_action != RCTL_ACTION_DENY) continue; available = rctl_available_resource(p, rule); if (available < minavailable) { minavailable = available; limit = rule->rr_amount; } } - rw_runlock(&rctl_lock); + RCTL_RUNLOCK(); /* * Return slightly less than actual value of the available * %cpu resource. This makes %cpu throttling more agressive * and lets us act sooner than the limits are already exceeded. */ if (limit != 0) { if (limit > 2 * RCTL_PCPU_SHIFT) minavailable -= RCTL_PCPU_SHIFT; else minavailable -= (limit / 2); } return (minavailable); } /* * Check whether the proc 'p' can allocate 'amount' of 'resource' in addition * to what it keeps allocated now. Returns non-zero if the allocation should * be denied, 0 otherwise. */ int rctl_enforce(struct proc *p, int resource, uint64_t amount) { static struct timeval log_lasttime, devctl_lasttime; static int log_curtime = 0, devctl_curtime = 0; struct rctl_rule *rule; struct rctl_rule_link *link; struct sbuf sb; int should_deny = 0; char *buf; ASSERT_RACCT_ENABLED(); - rw_rlock(&rctl_lock); + RCTL_RLOCK(); /* * There may be more than one matching rule; go through all of them. * Denial should be done last, after logging and sending signals. */ LIST_FOREACH(link, &p->p_racct->r_rule_links, rrl_next) { rule = link->rrl_rule; if (rule->rr_resource != resource) continue; if (!rctl_would_exceed(p, rule, amount)) { link->rrl_exceeded = 0; continue; } switch (rule->rr_action) { case RCTL_ACTION_DENY: should_deny = 1; continue; case RCTL_ACTION_LOG: /* * If rrl_exceeded != 0, it means we've already * logged a warning for this process. */ if (link->rrl_exceeded != 0) continue; /* * If the process state is not fully initialized yet, * we can't access most of the required fields, e.g. * p->p_comm. This happens when called from fork1(). * Ignore this rule for now; it will be processed just * after fork, when called from racct_proc_fork_done(). */ if (p->p_state != PRS_NORMAL) continue; if (!ppsratecheck(&log_lasttime, &log_curtime, rctl_log_rate_limit)) continue; buf = malloc(RCTL_LOG_BUFSIZE, M_RCTL, M_NOWAIT); if (buf == NULL) { printf("rctl_enforce: out of memory\n"); continue; } sbuf_new(&sb, buf, RCTL_LOG_BUFSIZE, SBUF_FIXEDLEN); rctl_rule_to_sbuf(&sb, rule); sbuf_finish(&sb); printf("rctl: rule \"%s\" matched by pid %d " "(%s), uid %d, jail %s\n", sbuf_data(&sb), p->p_pid, p->p_comm, p->p_ucred->cr_uid, p->p_ucred->cr_prison->pr_prison_racct->prr_name); sbuf_delete(&sb); free(buf, M_RCTL); link->rrl_exceeded = 1; continue; case RCTL_ACTION_DEVCTL: if (link->rrl_exceeded != 0) continue; if (p->p_state != PRS_NORMAL) continue; if (!ppsratecheck(&devctl_lasttime, &devctl_curtime, rctl_devctl_rate_limit)) continue; buf = malloc(RCTL_LOG_BUFSIZE, M_RCTL, M_NOWAIT); if (buf == NULL) { printf("rctl_enforce: out of memory\n"); continue; } sbuf_new(&sb, buf, RCTL_LOG_BUFSIZE, SBUF_FIXEDLEN); sbuf_printf(&sb, "rule="); rctl_rule_to_sbuf(&sb, rule); sbuf_printf(&sb, " pid=%d ruid=%d jail=%s", p->p_pid, p->p_ucred->cr_ruid, p->p_ucred->cr_prison->pr_prison_racct->prr_name); sbuf_finish(&sb); devctl_notify_f("RCTL", "rule", "matched", sbuf_data(&sb), M_NOWAIT); sbuf_delete(&sb); free(buf, M_RCTL); link->rrl_exceeded = 1; continue; default: if (link->rrl_exceeded != 0) continue; if (p->p_state != PRS_NORMAL) continue; KASSERT(rule->rr_action > 0 && rule->rr_action <= RCTL_ACTION_SIGNAL_MAX, ("rctl_enforce: unknown action %d", rule->rr_action)); /* * We're using the fact that RCTL_ACTION_SIG* values * are equal to their counterparts from sys/signal.h. */ kern_psignal(p, rule->rr_action); link->rrl_exceeded = 1; continue; } } - rw_runlock(&rctl_lock); + RCTL_RUNLOCK(); if (should_deny) { /* * Return fake error code; the caller should change it * into one proper for the situation - EFSIZ, ENOMEM etc. */ return (EDOOFUS); } return (0); } uint64_t rctl_get_limit(struct proc *p, int resource) { struct rctl_rule *rule; struct rctl_rule_link *link; uint64_t amount = UINT64_MAX; ASSERT_RACCT_ENABLED(); - rw_rlock(&rctl_lock); + RCTL_RLOCK(); /* * There may be more than one matching rule; go through all of them. * Denial should be done last, after logging and sending signals. */ LIST_FOREACH(link, &p->p_racct->r_rule_links, rrl_next) { rule = link->rrl_rule; if (rule->rr_resource != resource) continue; if (rule->rr_action != RCTL_ACTION_DENY) continue; if (rule->rr_amount < amount) amount = rule->rr_amount; } - rw_runlock(&rctl_lock); + RCTL_RUNLOCK(); return (amount); } uint64_t rctl_get_available(struct proc *p, int resource) { struct rctl_rule *rule; struct rctl_rule_link *link; int64_t available, minavailable, allocated; minavailable = INT64_MAX; ASSERT_RACCT_ENABLED(); - rw_rlock(&rctl_lock); + RCTL_RLOCK(); /* * There may be more than one matching rule; go through all of them. * Denial should be done last, after logging and sending signals. */ LIST_FOREACH(link, &p->p_racct->r_rule_links, rrl_next) { rule = link->rrl_rule; if (rule->rr_resource != resource) continue; if (rule->rr_action != RCTL_ACTION_DENY) continue; available = rctl_available_resource(p, rule); if (available < minavailable) minavailable = available; } - rw_runlock(&rctl_lock); + RCTL_RUNLOCK(); /* * XXX: Think about this _hard_. */ allocated = p->p_racct->r_resources[resource]; if (minavailable < INT64_MAX - allocated) minavailable += allocated; if (minavailable < 0) minavailable = 0; return (minavailable); } static int rctl_rule_matches(const struct rctl_rule *rule, const struct rctl_rule *filter) { ASSERT_RACCT_ENABLED(); if (filter->rr_subject_type != RCTL_SUBJECT_TYPE_UNDEFINED) { if (rule->rr_subject_type != filter->rr_subject_type) return (0); switch (filter->rr_subject_type) { case RCTL_SUBJECT_TYPE_PROCESS: if (filter->rr_subject.rs_proc != NULL && rule->rr_subject.rs_proc != filter->rr_subject.rs_proc) return (0); break; case RCTL_SUBJECT_TYPE_USER: if (filter->rr_subject.rs_uip != NULL && rule->rr_subject.rs_uip != filter->rr_subject.rs_uip) return (0); break; case RCTL_SUBJECT_TYPE_LOGINCLASS: if (filter->rr_subject.rs_loginclass != NULL && rule->rr_subject.rs_loginclass != filter->rr_subject.rs_loginclass) return (0); break; case RCTL_SUBJECT_TYPE_JAIL: if (filter->rr_subject.rs_prison_racct != NULL && rule->rr_subject.rs_prison_racct != filter->rr_subject.rs_prison_racct) return (0); break; default: panic("rctl_rule_matches: unknown subject type %d", filter->rr_subject_type); } } if (filter->rr_resource != RACCT_UNDEFINED) { if (rule->rr_resource != filter->rr_resource) return (0); } if (filter->rr_action != RCTL_ACTION_UNDEFINED) { if (rule->rr_action != filter->rr_action) return (0); } if (filter->rr_amount != RCTL_AMOUNT_UNDEFINED) { if (rule->rr_amount != filter->rr_amount) return (0); } if (filter->rr_per != RCTL_SUBJECT_TYPE_UNDEFINED) { if (rule->rr_per != filter->rr_per) return (0); } return (1); } static int str2value(const char *str, int *value, struct dict *table) { int i; if (value == NULL) return (EINVAL); for (i = 0; table[i].d_name != NULL; i++) { if (strcasecmp(table[i].d_name, str) == 0) { *value = table[i].d_value; return (0); } } return (EINVAL); } static int str2id(const char *str, id_t *value) { char *end; if (str == NULL) return (EINVAL); *value = strtoul(str, &end, 10); if ((size_t)(end - str) != strlen(str)) return (EINVAL); return (0); } static int str2int64(const char *str, int64_t *value) { char *end; if (str == NULL) return (EINVAL); *value = strtoul(str, &end, 10); if ((size_t)(end - str) != strlen(str)) return (EINVAL); if (*value < 0) return (ERANGE); return (0); } /* * Connect the rule to the racct, increasing refcount for the rule. */ static void rctl_racct_add_rule(struct racct *racct, struct rctl_rule *rule) { struct rctl_rule_link *link; ASSERT_RACCT_ENABLED(); KASSERT(rctl_rule_fully_specified(rule), ("rule not fully specified")); rctl_rule_acquire(rule); link = uma_zalloc(rctl_rule_link_zone, M_WAITOK); link->rrl_rule = rule; link->rrl_exceeded = 0; - rw_wlock(&rctl_lock); + RCTL_WLOCK(); LIST_INSERT_HEAD(&racct->r_rule_links, link, rrl_next); - rw_wunlock(&rctl_lock); + RCTL_WUNLOCK(); } static int rctl_racct_add_rule_locked(struct racct *racct, struct rctl_rule *rule) { struct rctl_rule_link *link; ASSERT_RACCT_ENABLED(); KASSERT(rctl_rule_fully_specified(rule), ("rule not fully specified")); - rw_assert(&rctl_lock, RA_WLOCKED); + RCTL_WLOCK_ASSERT(); link = uma_zalloc(rctl_rule_link_zone, M_NOWAIT); if (link == NULL) return (ENOMEM); rctl_rule_acquire(rule); link->rrl_rule = rule; link->rrl_exceeded = 0; LIST_INSERT_HEAD(&racct->r_rule_links, link, rrl_next); return (0); } /* * Remove limits for a rules matching the filter and release * the refcounts for the rules, possibly freeing them. Returns * the number of limit structures removed. */ static int rctl_racct_remove_rules(struct racct *racct, const struct rctl_rule *filter) { int removed = 0; struct rctl_rule_link *link, *linktmp; ASSERT_RACCT_ENABLED(); - rw_assert(&rctl_lock, RA_WLOCKED); + RCTL_WLOCK_ASSERT(); LIST_FOREACH_SAFE(link, &racct->r_rule_links, rrl_next, linktmp) { if (!rctl_rule_matches(link->rrl_rule, filter)) continue; LIST_REMOVE(link, rrl_next); rctl_rule_release(link->rrl_rule); uma_zfree(rctl_rule_link_zone, link); removed++; } return (removed); } static void rctl_rule_acquire_subject(struct rctl_rule *rule) { ASSERT_RACCT_ENABLED(); switch (rule->rr_subject_type) { case RCTL_SUBJECT_TYPE_UNDEFINED: case RCTL_SUBJECT_TYPE_PROCESS: break; case RCTL_SUBJECT_TYPE_JAIL: if (rule->rr_subject.rs_prison_racct != NULL) prison_racct_hold(rule->rr_subject.rs_prison_racct); break; case RCTL_SUBJECT_TYPE_USER: if (rule->rr_subject.rs_uip != NULL) uihold(rule->rr_subject.rs_uip); break; case RCTL_SUBJECT_TYPE_LOGINCLASS: if (rule->rr_subject.rs_loginclass != NULL) loginclass_hold(rule->rr_subject.rs_loginclass); break; default: panic("rctl_rule_acquire_subject: unknown subject type %d", rule->rr_subject_type); } } static void rctl_rule_release_subject(struct rctl_rule *rule) { ASSERT_RACCT_ENABLED(); switch (rule->rr_subject_type) { case RCTL_SUBJECT_TYPE_UNDEFINED: case RCTL_SUBJECT_TYPE_PROCESS: break; case RCTL_SUBJECT_TYPE_JAIL: if (rule->rr_subject.rs_prison_racct != NULL) prison_racct_free(rule->rr_subject.rs_prison_racct); break; case RCTL_SUBJECT_TYPE_USER: if (rule->rr_subject.rs_uip != NULL) uifree(rule->rr_subject.rs_uip); break; case RCTL_SUBJECT_TYPE_LOGINCLASS: if (rule->rr_subject.rs_loginclass != NULL) loginclass_free(rule->rr_subject.rs_loginclass); break; default: panic("rctl_rule_release_subject: unknown subject type %d", rule->rr_subject_type); } } struct rctl_rule * rctl_rule_alloc(int flags) { struct rctl_rule *rule; ASSERT_RACCT_ENABLED(); rule = uma_zalloc(rctl_rule_zone, flags); if (rule == NULL) return (NULL); rule->rr_subject_type = RCTL_SUBJECT_TYPE_UNDEFINED; rule->rr_subject.rs_proc = NULL; rule->rr_subject.rs_uip = NULL; rule->rr_subject.rs_loginclass = NULL; rule->rr_subject.rs_prison_racct = NULL; rule->rr_per = RCTL_SUBJECT_TYPE_UNDEFINED; rule->rr_resource = RACCT_UNDEFINED; rule->rr_action = RCTL_ACTION_UNDEFINED; rule->rr_amount = RCTL_AMOUNT_UNDEFINED; refcount_init(&rule->rr_refcount, 1); return (rule); } struct rctl_rule * rctl_rule_duplicate(const struct rctl_rule *rule, int flags) { struct rctl_rule *copy; ASSERT_RACCT_ENABLED(); copy = uma_zalloc(rctl_rule_zone, flags); if (copy == NULL) return (NULL); copy->rr_subject_type = rule->rr_subject_type; copy->rr_subject.rs_proc = rule->rr_subject.rs_proc; copy->rr_subject.rs_uip = rule->rr_subject.rs_uip; copy->rr_subject.rs_loginclass = rule->rr_subject.rs_loginclass; copy->rr_subject.rs_prison_racct = rule->rr_subject.rs_prison_racct; copy->rr_per = rule->rr_per; copy->rr_resource = rule->rr_resource; copy->rr_action = rule->rr_action; copy->rr_amount = rule->rr_amount; refcount_init(©->rr_refcount, 1); rctl_rule_acquire_subject(copy); return (copy); } void rctl_rule_acquire(struct rctl_rule *rule) { ASSERT_RACCT_ENABLED(); KASSERT(rule->rr_refcount > 0, ("rule->rr_refcount <= 0")); refcount_acquire(&rule->rr_refcount); } static void rctl_rule_free(void *context, int pending) { struct rctl_rule *rule; rule = (struct rctl_rule *)context; ASSERT_RACCT_ENABLED(); KASSERT(rule->rr_refcount == 0, ("rule->rr_refcount != 0")); /* * We don't need locking here; rule is guaranteed to be inaccessible. */ rctl_rule_release_subject(rule); uma_zfree(rctl_rule_zone, rule); } void rctl_rule_release(struct rctl_rule *rule) { ASSERT_RACCT_ENABLED(); KASSERT(rule->rr_refcount > 0, ("rule->rr_refcount <= 0")); if (refcount_release(&rule->rr_refcount)) { /* * rctl_rule_release() is often called when iterating * over all the uidinfo structures in the system, * holding uihashtbl_lock. Since rctl_rule_free() * might end up calling uifree(), this would lead * to lock recursion. Use taskqueue to avoid this. */ TASK_INIT(&rule->rr_task, 0, rctl_rule_free, rule); taskqueue_enqueue(taskqueue_thread, &rule->rr_task); } } static int rctl_rule_fully_specified(const struct rctl_rule *rule) { ASSERT_RACCT_ENABLED(); switch (rule->rr_subject_type) { case RCTL_SUBJECT_TYPE_UNDEFINED: return (0); case RCTL_SUBJECT_TYPE_PROCESS: if (rule->rr_subject.rs_proc == NULL) return (0); break; case RCTL_SUBJECT_TYPE_USER: if (rule->rr_subject.rs_uip == NULL) return (0); break; case RCTL_SUBJECT_TYPE_LOGINCLASS: if (rule->rr_subject.rs_loginclass == NULL) return (0); break; case RCTL_SUBJECT_TYPE_JAIL: if (rule->rr_subject.rs_prison_racct == NULL) return (0); break; default: panic("rctl_rule_fully_specified: unknown subject type %d", rule->rr_subject_type); } if (rule->rr_resource == RACCT_UNDEFINED) return (0); if (rule->rr_action == RCTL_ACTION_UNDEFINED) return (0); if (rule->rr_amount == RCTL_AMOUNT_UNDEFINED) return (0); if (rule->rr_per == RCTL_SUBJECT_TYPE_UNDEFINED) return (0); return (1); } static int rctl_string_to_rule(char *rulestr, struct rctl_rule **rulep) { int error = 0; char *subjectstr, *subject_idstr, *resourcestr, *actionstr, *amountstr, *perstr; struct rctl_rule *rule; id_t id; ASSERT_RACCT_ENABLED(); rule = rctl_rule_alloc(M_WAITOK); subjectstr = strsep(&rulestr, ":"); subject_idstr = strsep(&rulestr, ":"); resourcestr = strsep(&rulestr, ":"); actionstr = strsep(&rulestr, "=/"); amountstr = strsep(&rulestr, "/"); perstr = rulestr; if (subjectstr == NULL || subjectstr[0] == '\0') rule->rr_subject_type = RCTL_SUBJECT_TYPE_UNDEFINED; else { error = str2value(subjectstr, &rule->rr_subject_type, subjectnames); if (error != 0) goto out; } if (subject_idstr == NULL || subject_idstr[0] == '\0') { rule->rr_subject.rs_proc = NULL; rule->rr_subject.rs_uip = NULL; rule->rr_subject.rs_loginclass = NULL; rule->rr_subject.rs_prison_racct = NULL; } else { switch (rule->rr_subject_type) { case RCTL_SUBJECT_TYPE_UNDEFINED: error = EINVAL; goto out; case RCTL_SUBJECT_TYPE_PROCESS: error = str2id(subject_idstr, &id); if (error != 0) goto out; sx_assert(&allproc_lock, SA_LOCKED); rule->rr_subject.rs_proc = pfind(id); if (rule->rr_subject.rs_proc == NULL) { error = ESRCH; goto out; } PROC_UNLOCK(rule->rr_subject.rs_proc); break; case RCTL_SUBJECT_TYPE_USER: error = str2id(subject_idstr, &id); if (error != 0) goto out; rule->rr_subject.rs_uip = uifind(id); break; case RCTL_SUBJECT_TYPE_LOGINCLASS: rule->rr_subject.rs_loginclass = loginclass_find(subject_idstr); if (rule->rr_subject.rs_loginclass == NULL) { error = ENAMETOOLONG; goto out; } break; case RCTL_SUBJECT_TYPE_JAIL: rule->rr_subject.rs_prison_racct = prison_racct_find(subject_idstr); if (rule->rr_subject.rs_prison_racct == NULL) { error = ENAMETOOLONG; goto out; } break; default: panic("rctl_string_to_rule: unknown subject type %d", rule->rr_subject_type); } } if (resourcestr == NULL || resourcestr[0] == '\0') rule->rr_resource = RACCT_UNDEFINED; else { error = str2value(resourcestr, &rule->rr_resource, resourcenames); if (error != 0) goto out; } if (actionstr == NULL || actionstr[0] == '\0') rule->rr_action = RCTL_ACTION_UNDEFINED; else { error = str2value(actionstr, &rule->rr_action, actionnames); if (error != 0) goto out; } if (amountstr == NULL || amountstr[0] == '\0') rule->rr_amount = RCTL_AMOUNT_UNDEFINED; else { error = str2int64(amountstr, &rule->rr_amount); if (error != 0) goto out; if (RACCT_IS_IN_MILLIONS(rule->rr_resource)) { if (rule->rr_amount > INT64_MAX / 1000000) { error = ERANGE; goto out; } rule->rr_amount *= 1000000; } } if (perstr == NULL || perstr[0] == '\0') rule->rr_per = RCTL_SUBJECT_TYPE_UNDEFINED; else { error = str2value(perstr, &rule->rr_per, subjectnames); if (error != 0) goto out; } out: if (error == 0) *rulep = rule; else rctl_rule_release(rule); return (error); } /* * Link a rule with all the subjects it applies to. */ int rctl_rule_add(struct rctl_rule *rule) { struct proc *p; struct ucred *cred; struct uidinfo *uip; struct prison *pr; struct prison_racct *prr; struct loginclass *lc; struct rctl_rule *rule2; int match; ASSERT_RACCT_ENABLED(); KASSERT(rctl_rule_fully_specified(rule), ("rule not fully specified")); /* * Some rules just don't make sense. Note that the one below * cannot be rewritten using RACCT_IS_DENIABLE(); the RACCT_PCTCPU, * for example, is not deniable in the racct sense, but the * limit is enforced in a different way, so "deny" rules for %CPU * do make sense. */ if (rule->rr_action == RCTL_ACTION_DENY && (rule->rr_resource == RACCT_CPU || rule->rr_resource == RACCT_WALLCLOCK)) return (EOPNOTSUPP); if (rule->rr_per == RCTL_SUBJECT_TYPE_PROCESS && RACCT_IS_SLOPPY(rule->rr_resource)) return (EOPNOTSUPP); /* * Make sure there are no duplicated rules. Also, for the "deny" * rules, remove ones differing only by "amount". */ if (rule->rr_action == RCTL_ACTION_DENY) { rule2 = rctl_rule_duplicate(rule, M_WAITOK); rule2->rr_amount = RCTL_AMOUNT_UNDEFINED; rctl_rule_remove(rule2); rctl_rule_release(rule2); } else rctl_rule_remove(rule); switch (rule->rr_subject_type) { case RCTL_SUBJECT_TYPE_PROCESS: p = rule->rr_subject.rs_proc; KASSERT(p != NULL, ("rctl_rule_add: NULL proc")); rctl_racct_add_rule(p->p_racct, rule); /* * In case of per-process rule, we don't have anything more * to do. */ return (0); case RCTL_SUBJECT_TYPE_USER: uip = rule->rr_subject.rs_uip; KASSERT(uip != NULL, ("rctl_rule_add: NULL uip")); rctl_racct_add_rule(uip->ui_racct, rule); break; case RCTL_SUBJECT_TYPE_LOGINCLASS: lc = rule->rr_subject.rs_loginclass; KASSERT(lc != NULL, ("rctl_rule_add: NULL loginclass")); rctl_racct_add_rule(lc->lc_racct, rule); break; case RCTL_SUBJECT_TYPE_JAIL: prr = rule->rr_subject.rs_prison_racct; KASSERT(prr != NULL, ("rctl_rule_add: NULL pr")); rctl_racct_add_rule(prr->prr_racct, rule); break; default: panic("rctl_rule_add: unknown subject type %d", rule->rr_subject_type); } /* * Now go through all the processes and add the new rule to the ones * it applies to. */ sx_assert(&allproc_lock, SA_LOCKED); FOREACH_PROC_IN_SYSTEM(p) { cred = p->p_ucred; switch (rule->rr_subject_type) { case RCTL_SUBJECT_TYPE_USER: if (cred->cr_uidinfo == rule->rr_subject.rs_uip || cred->cr_ruidinfo == rule->rr_subject.rs_uip) break; continue; case RCTL_SUBJECT_TYPE_LOGINCLASS: if (cred->cr_loginclass == rule->rr_subject.rs_loginclass) break; continue; case RCTL_SUBJECT_TYPE_JAIL: match = 0; for (pr = cred->cr_prison; pr != NULL; pr = pr->pr_parent) { if (pr->pr_prison_racct == rule->rr_subject.rs_prison_racct) { match = 1; break; } } if (match) break; continue; default: panic("rctl_rule_add: unknown subject type %d", rule->rr_subject_type); } rctl_racct_add_rule(p->p_racct, rule); } return (0); } static void rctl_rule_pre_callback(void) { - rw_wlock(&rctl_lock); + RCTL_WLOCK(); } static void rctl_rule_post_callback(void) { - rw_wunlock(&rctl_lock); + RCTL_WUNLOCK(); } static void rctl_rule_remove_callback(struct racct *racct, void *arg2, void *arg3) { struct rctl_rule *filter = (struct rctl_rule *)arg2; int found = 0; ASSERT_RACCT_ENABLED(); - rw_assert(&rctl_lock, RA_WLOCKED); + RCTL_WLOCK_ASSERT(); found += rctl_racct_remove_rules(racct, filter); *((int *)arg3) += found; } /* * Remove all rules that match the filter. */ int rctl_rule_remove(struct rctl_rule *filter) { int found = 0; struct proc *p; ASSERT_RACCT_ENABLED(); if (filter->rr_subject_type == RCTL_SUBJECT_TYPE_PROCESS && filter->rr_subject.rs_proc != NULL) { p = filter->rr_subject.rs_proc; - rw_wlock(&rctl_lock); + RCTL_WLOCK(); found = rctl_racct_remove_rules(p->p_racct, filter); - rw_wunlock(&rctl_lock); + RCTL_WUNLOCK(); if (found) return (0); return (ESRCH); } loginclass_racct_foreach(rctl_rule_remove_callback, rctl_rule_pre_callback, rctl_rule_post_callback, filter, (void *)&found); ui_racct_foreach(rctl_rule_remove_callback, rctl_rule_pre_callback, rctl_rule_post_callback, filter, (void *)&found); prison_racct_foreach(rctl_rule_remove_callback, rctl_rule_pre_callback, rctl_rule_post_callback, filter, (void *)&found); sx_assert(&allproc_lock, SA_LOCKED); - rw_wlock(&rctl_lock); + RCTL_WLOCK(); FOREACH_PROC_IN_SYSTEM(p) { found += rctl_racct_remove_rules(p->p_racct, filter); } - rw_wunlock(&rctl_lock); + RCTL_WUNLOCK(); if (found) return (0); return (ESRCH); } /* * Appends a rule to the sbuf. */ static void rctl_rule_to_sbuf(struct sbuf *sb, const struct rctl_rule *rule) { int64_t amount; ASSERT_RACCT_ENABLED(); sbuf_printf(sb, "%s:", rctl_subject_type_name(rule->rr_subject_type)); switch (rule->rr_subject_type) { case RCTL_SUBJECT_TYPE_PROCESS: if (rule->rr_subject.rs_proc == NULL) sbuf_printf(sb, ":"); else sbuf_printf(sb, "%d:", rule->rr_subject.rs_proc->p_pid); break; case RCTL_SUBJECT_TYPE_USER: if (rule->rr_subject.rs_uip == NULL) sbuf_printf(sb, ":"); else sbuf_printf(sb, "%d:", rule->rr_subject.rs_uip->ui_uid); break; case RCTL_SUBJECT_TYPE_LOGINCLASS: if (rule->rr_subject.rs_loginclass == NULL) sbuf_printf(sb, ":"); else sbuf_printf(sb, "%s:", rule->rr_subject.rs_loginclass->lc_name); break; case RCTL_SUBJECT_TYPE_JAIL: if (rule->rr_subject.rs_prison_racct == NULL) sbuf_printf(sb, ":"); else sbuf_printf(sb, "%s:", rule->rr_subject.rs_prison_racct->prr_name); break; default: panic("rctl_rule_to_sbuf: unknown subject type %d", rule->rr_subject_type); } amount = rule->rr_amount; if (amount != RCTL_AMOUNT_UNDEFINED && RACCT_IS_IN_MILLIONS(rule->rr_resource)) amount /= 1000000; sbuf_printf(sb, "%s:%s=%jd", rctl_resource_name(rule->rr_resource), rctl_action_name(rule->rr_action), amount); if (rule->rr_per != rule->rr_subject_type) sbuf_printf(sb, "/%s", rctl_subject_type_name(rule->rr_per)); } /* * Routine used by RCTL syscalls to read in input string. */ static int rctl_read_inbuf(char **inputstr, const char *inbufp, size_t inbuflen) { int error; char *str; ASSERT_RACCT_ENABLED(); if (inbuflen <= 0) return (EINVAL); if (inbuflen > RCTL_MAX_INBUFSIZE) return (E2BIG); str = malloc(inbuflen + 1, M_RCTL, M_WAITOK); error = copyinstr(inbufp, str, inbuflen, NULL); if (error != 0) { free(str, M_RCTL); return (error); } *inputstr = str; return (0); } /* * Routine used by RCTL syscalls to write out output string. */ static int rctl_write_outbuf(struct sbuf *outputsbuf, char *outbufp, size_t outbuflen) { int error; ASSERT_RACCT_ENABLED(); if (outputsbuf == NULL) return (0); sbuf_finish(outputsbuf); if (outbuflen < sbuf_len(outputsbuf) + 1) { sbuf_delete(outputsbuf); return (ERANGE); } error = copyout(sbuf_data(outputsbuf), outbufp, sbuf_len(outputsbuf) + 1); sbuf_delete(outputsbuf); return (error); } static struct sbuf * rctl_racct_to_sbuf(struct racct *racct, int sloppy) { int i; int64_t amount; struct sbuf *sb; ASSERT_RACCT_ENABLED(); sb = sbuf_new_auto(); for (i = 0; i <= RACCT_MAX; i++) { if (sloppy == 0 && RACCT_IS_SLOPPY(i)) continue; amount = racct->r_resources[i]; if (RACCT_IS_IN_MILLIONS(i)) amount /= 1000000; sbuf_printf(sb, "%s=%jd,", rctl_resource_name(i), amount); } sbuf_setpos(sb, sbuf_len(sb) - 1); return (sb); } int sys_rctl_get_racct(struct thread *td, struct rctl_get_racct_args *uap) { int error; char *inputstr; struct rctl_rule *filter; struct sbuf *outputsbuf = NULL; struct proc *p; struct uidinfo *uip; struct loginclass *lc; struct prison_racct *prr; if (!racct_enable) return (ENOSYS); error = priv_check(td, PRIV_RCTL_GET_RACCT); if (error != 0) return (error); error = rctl_read_inbuf(&inputstr, uap->inbufp, uap->inbuflen); if (error != 0) return (error); sx_slock(&allproc_lock); error = rctl_string_to_rule(inputstr, &filter); free(inputstr, M_RCTL); if (error != 0) { sx_sunlock(&allproc_lock); return (error); } switch (filter->rr_subject_type) { case RCTL_SUBJECT_TYPE_PROCESS: p = filter->rr_subject.rs_proc; if (p == NULL) { error = EINVAL; goto out; } outputsbuf = rctl_racct_to_sbuf(p->p_racct, 0); break; case RCTL_SUBJECT_TYPE_USER: uip = filter->rr_subject.rs_uip; if (uip == NULL) { error = EINVAL; goto out; } outputsbuf = rctl_racct_to_sbuf(uip->ui_racct, 1); break; case RCTL_SUBJECT_TYPE_LOGINCLASS: lc = filter->rr_subject.rs_loginclass; if (lc == NULL) { error = EINVAL; goto out; } outputsbuf = rctl_racct_to_sbuf(lc->lc_racct, 1); break; case RCTL_SUBJECT_TYPE_JAIL: prr = filter->rr_subject.rs_prison_racct; if (prr == NULL) { error = EINVAL; goto out; } outputsbuf = rctl_racct_to_sbuf(prr->prr_racct, 1); break; default: error = EINVAL; } out: rctl_rule_release(filter); sx_sunlock(&allproc_lock); if (error != 0) return (error); error = rctl_write_outbuf(outputsbuf, uap->outbufp, uap->outbuflen); return (error); } static void rctl_get_rules_callback(struct racct *racct, void *arg2, void *arg3) { struct rctl_rule *filter = (struct rctl_rule *)arg2; struct rctl_rule_link *link; struct sbuf *sb = (struct sbuf *)arg3; ASSERT_RACCT_ENABLED(); - rw_assert(&rctl_lock, RA_LOCKED); + RCTL_LOCK_ASSERT(); LIST_FOREACH(link, &racct->r_rule_links, rrl_next) { if (!rctl_rule_matches(link->rrl_rule, filter)) continue; rctl_rule_to_sbuf(sb, link->rrl_rule); sbuf_printf(sb, ","); } } int sys_rctl_get_rules(struct thread *td, struct rctl_get_rules_args *uap) { int error; size_t bufsize; char *inputstr, *buf; struct sbuf *sb; struct rctl_rule *filter; struct rctl_rule_link *link; struct proc *p; if (!racct_enable) return (ENOSYS); error = priv_check(td, PRIV_RCTL_GET_RULES); if (error != 0) return (error); error = rctl_read_inbuf(&inputstr, uap->inbufp, uap->inbuflen); if (error != 0) return (error); sx_slock(&allproc_lock); error = rctl_string_to_rule(inputstr, &filter); free(inputstr, M_RCTL); if (error != 0) { sx_sunlock(&allproc_lock); return (error); } bufsize = uap->outbuflen; if (bufsize > rctl_maxbufsize) { sx_sunlock(&allproc_lock); return (E2BIG); } buf = malloc(bufsize, M_RCTL, M_WAITOK); sb = sbuf_new(NULL, buf, bufsize, SBUF_FIXEDLEN); KASSERT(sb != NULL, ("sbuf_new failed")); FOREACH_PROC_IN_SYSTEM(p) { - rw_rlock(&rctl_lock); + RCTL_RLOCK(); LIST_FOREACH(link, &p->p_racct->r_rule_links, rrl_next) { /* * Non-process rules will be added to the buffer later. * Adding them here would result in duplicated output. */ if (link->rrl_rule->rr_subject_type != RCTL_SUBJECT_TYPE_PROCESS) continue; if (!rctl_rule_matches(link->rrl_rule, filter)) continue; rctl_rule_to_sbuf(sb, link->rrl_rule); sbuf_printf(sb, ","); } - rw_runlock(&rctl_lock); + RCTL_RUNLOCK(); } loginclass_racct_foreach(rctl_get_rules_callback, rctl_rule_pre_callback, rctl_rule_post_callback, filter, sb); ui_racct_foreach(rctl_get_rules_callback, rctl_rule_pre_callback, rctl_rule_post_callback, filter, sb); prison_racct_foreach(rctl_get_rules_callback, rctl_rule_pre_callback, rctl_rule_post_callback, filter, sb); if (sbuf_error(sb) == ENOMEM) { error = ERANGE; goto out; } /* * Remove trailing ",". */ if (sbuf_len(sb) > 0) sbuf_setpos(sb, sbuf_len(sb) - 1); error = rctl_write_outbuf(sb, uap->outbufp, uap->outbuflen); out: rctl_rule_release(filter); sx_sunlock(&allproc_lock); free(buf, M_RCTL); return (error); } int sys_rctl_get_limits(struct thread *td, struct rctl_get_limits_args *uap) { int error; size_t bufsize; char *inputstr, *buf; struct sbuf *sb; struct rctl_rule *filter; struct rctl_rule_link *link; if (!racct_enable) return (ENOSYS); error = priv_check(td, PRIV_RCTL_GET_LIMITS); if (error != 0) return (error); error = rctl_read_inbuf(&inputstr, uap->inbufp, uap->inbuflen); if (error != 0) return (error); sx_slock(&allproc_lock); error = rctl_string_to_rule(inputstr, &filter); free(inputstr, M_RCTL); if (error != 0) { sx_sunlock(&allproc_lock); return (error); } if (filter->rr_subject_type == RCTL_SUBJECT_TYPE_UNDEFINED) { rctl_rule_release(filter); sx_sunlock(&allproc_lock); return (EINVAL); } if (filter->rr_subject_type != RCTL_SUBJECT_TYPE_PROCESS) { rctl_rule_release(filter); sx_sunlock(&allproc_lock); return (EOPNOTSUPP); } if (filter->rr_subject.rs_proc == NULL) { rctl_rule_release(filter); sx_sunlock(&allproc_lock); return (EINVAL); } bufsize = uap->outbuflen; if (bufsize > rctl_maxbufsize) { rctl_rule_release(filter); sx_sunlock(&allproc_lock); return (E2BIG); } buf = malloc(bufsize, M_RCTL, M_WAITOK); sb = sbuf_new(NULL, buf, bufsize, SBUF_FIXEDLEN); KASSERT(sb != NULL, ("sbuf_new failed")); - rw_rlock(&rctl_lock); + RCTL_RLOCK(); LIST_FOREACH(link, &filter->rr_subject.rs_proc->p_racct->r_rule_links, rrl_next) { rctl_rule_to_sbuf(sb, link->rrl_rule); sbuf_printf(sb, ","); } - rw_runlock(&rctl_lock); + RCTL_RUNLOCK(); if (sbuf_error(sb) == ENOMEM) { error = ERANGE; goto out; } /* * Remove trailing ",". */ if (sbuf_len(sb) > 0) sbuf_setpos(sb, sbuf_len(sb) - 1); error = rctl_write_outbuf(sb, uap->outbufp, uap->outbuflen); out: rctl_rule_release(filter); sx_sunlock(&allproc_lock); free(buf, M_RCTL); return (error); } int sys_rctl_add_rule(struct thread *td, struct rctl_add_rule_args *uap) { int error; struct rctl_rule *rule; char *inputstr; if (!racct_enable) return (ENOSYS); error = priv_check(td, PRIV_RCTL_ADD_RULE); if (error != 0) return (error); error = rctl_read_inbuf(&inputstr, uap->inbufp, uap->inbuflen); if (error != 0) return (error); sx_slock(&allproc_lock); error = rctl_string_to_rule(inputstr, &rule); free(inputstr, M_RCTL); if (error != 0) { sx_sunlock(&allproc_lock); return (error); } /* * The 'per' part of a rule is optional. */ if (rule->rr_per == RCTL_SUBJECT_TYPE_UNDEFINED && rule->rr_subject_type != RCTL_SUBJECT_TYPE_UNDEFINED) rule->rr_per = rule->rr_subject_type; if (!rctl_rule_fully_specified(rule)) { error = EINVAL; goto out; } error = rctl_rule_add(rule); out: rctl_rule_release(rule); sx_sunlock(&allproc_lock); return (error); } int sys_rctl_remove_rule(struct thread *td, struct rctl_remove_rule_args *uap) { int error; struct rctl_rule *filter; char *inputstr; if (!racct_enable) return (ENOSYS); error = priv_check(td, PRIV_RCTL_REMOVE_RULE); if (error != 0) return (error); error = rctl_read_inbuf(&inputstr, uap->inbufp, uap->inbuflen); if (error != 0) return (error); sx_slock(&allproc_lock); error = rctl_string_to_rule(inputstr, &filter); free(inputstr, M_RCTL); if (error != 0) { sx_sunlock(&allproc_lock); return (error); } error = rctl_rule_remove(filter); rctl_rule_release(filter); sx_sunlock(&allproc_lock); return (error); } /* * Update RCTL rule list after credential change. */ void rctl_proc_ucred_changed(struct proc *p, struct ucred *newcred) { int rulecnt, i; struct rctl_rule_link *link, *newlink; struct uidinfo *newuip; struct loginclass *newlc; struct prison_racct *newprr; LIST_HEAD(, rctl_rule_link) newrules; ASSERT_RACCT_ENABLED(); newuip = newcred->cr_ruidinfo; newlc = newcred->cr_loginclass; newprr = newcred->cr_prison->pr_prison_racct; LIST_INIT(&newrules); again: /* * First, count the rules that apply to the process with new * credentials. */ rulecnt = 0; - rw_rlock(&rctl_lock); + RCTL_RLOCK(); LIST_FOREACH(link, &p->p_racct->r_rule_links, rrl_next) { if (link->rrl_rule->rr_subject_type == RCTL_SUBJECT_TYPE_PROCESS) rulecnt++; } LIST_FOREACH(link, &newuip->ui_racct->r_rule_links, rrl_next) rulecnt++; LIST_FOREACH(link, &newlc->lc_racct->r_rule_links, rrl_next) rulecnt++; LIST_FOREACH(link, &newprr->prr_racct->r_rule_links, rrl_next) rulecnt++; - rw_runlock(&rctl_lock); + RCTL_RUNLOCK(); /* * Create temporary list. We've dropped the rctl_lock in order * to use M_WAITOK. */ for (i = 0; i < rulecnt; i++) { newlink = uma_zalloc(rctl_rule_link_zone, M_WAITOK); newlink->rrl_rule = NULL; newlink->rrl_exceeded = 0; LIST_INSERT_HEAD(&newrules, newlink, rrl_next); } newlink = LIST_FIRST(&newrules); /* * Assign rules to the newly allocated list entries. */ - rw_wlock(&rctl_lock); + RCTL_WLOCK(); LIST_FOREACH(link, &p->p_racct->r_rule_links, rrl_next) { if (link->rrl_rule->rr_subject_type == RCTL_SUBJECT_TYPE_PROCESS) { if (newlink == NULL) goto goaround; rctl_rule_acquire(link->rrl_rule); newlink->rrl_rule = link->rrl_rule; newlink->rrl_exceeded = link->rrl_exceeded; newlink = LIST_NEXT(newlink, rrl_next); rulecnt--; } } LIST_FOREACH(link, &newuip->ui_racct->r_rule_links, rrl_next) { if (newlink == NULL) goto goaround; rctl_rule_acquire(link->rrl_rule); newlink->rrl_rule = link->rrl_rule; newlink->rrl_exceeded = link->rrl_exceeded; newlink = LIST_NEXT(newlink, rrl_next); rulecnt--; } LIST_FOREACH(link, &newlc->lc_racct->r_rule_links, rrl_next) { if (newlink == NULL) goto goaround; rctl_rule_acquire(link->rrl_rule); newlink->rrl_rule = link->rrl_rule; newlink->rrl_exceeded = link->rrl_exceeded; newlink = LIST_NEXT(newlink, rrl_next); rulecnt--; } LIST_FOREACH(link, &newprr->prr_racct->r_rule_links, rrl_next) { if (newlink == NULL) goto goaround; rctl_rule_acquire(link->rrl_rule); newlink->rrl_rule = link->rrl_rule; newlink->rrl_exceeded = link->rrl_exceeded; newlink = LIST_NEXT(newlink, rrl_next); rulecnt--; } if (rulecnt == 0) { /* * Free the old rule list. */ while (!LIST_EMPTY(&p->p_racct->r_rule_links)) { link = LIST_FIRST(&p->p_racct->r_rule_links); LIST_REMOVE(link, rrl_next); rctl_rule_release(link->rrl_rule); uma_zfree(rctl_rule_link_zone, link); } /* * Replace lists and we're done. * * XXX: Is there any way to switch list heads instead * of iterating here? */ while (!LIST_EMPTY(&newrules)) { newlink = LIST_FIRST(&newrules); LIST_REMOVE(newlink, rrl_next); LIST_INSERT_HEAD(&p->p_racct->r_rule_links, newlink, rrl_next); } - rw_wunlock(&rctl_lock); + RCTL_WUNLOCK(); return; } goaround: - rw_wunlock(&rctl_lock); + RCTL_WUNLOCK(); /* * Rule list changed while we were not holding the rctl_lock. * Free the new list and try again. */ while (!LIST_EMPTY(&newrules)) { newlink = LIST_FIRST(&newrules); LIST_REMOVE(newlink, rrl_next); if (newlink->rrl_rule != NULL) rctl_rule_release(newlink->rrl_rule); uma_zfree(rctl_rule_link_zone, newlink); } goto again; } /* * Assign RCTL rules to the newly created process. */ int rctl_proc_fork(struct proc *parent, struct proc *child) { int error; struct rctl_rule_link *link; struct rctl_rule *rule; LIST_INIT(&child->p_racct->r_rule_links); ASSERT_RACCT_ENABLED(); KASSERT(parent->p_racct != NULL, ("process without racct; p = %p", parent)); - rw_wlock(&rctl_lock); + RCTL_WLOCK(); /* * Go through limits applicable to the parent and assign them * to the child. Rules with 'process' subject have to be duplicated * in order to make their rr_subject point to the new process. */ LIST_FOREACH(link, &parent->p_racct->r_rule_links, rrl_next) { if (link->rrl_rule->rr_subject_type == RCTL_SUBJECT_TYPE_PROCESS) { rule = rctl_rule_duplicate(link->rrl_rule, M_NOWAIT); if (rule == NULL) goto fail; KASSERT(rule->rr_subject.rs_proc == parent, ("rule->rr_subject.rs_proc != parent")); rule->rr_subject.rs_proc = child; error = rctl_racct_add_rule_locked(child->p_racct, rule); rctl_rule_release(rule); if (error != 0) goto fail; } else { error = rctl_racct_add_rule_locked(child->p_racct, link->rrl_rule); if (error != 0) goto fail; } } - rw_wunlock(&rctl_lock); + RCTL_WUNLOCK(); return (0); fail: while (!LIST_EMPTY(&child->p_racct->r_rule_links)) { link = LIST_FIRST(&child->p_racct->r_rule_links); LIST_REMOVE(link, rrl_next); rctl_rule_release(link->rrl_rule); uma_zfree(rctl_rule_link_zone, link); } - rw_wunlock(&rctl_lock); + RCTL_WUNLOCK(); return (EAGAIN); } /* * Release rules attached to the racct. */ void rctl_racct_release(struct racct *racct) { struct rctl_rule_link *link; ASSERT_RACCT_ENABLED(); - rw_wlock(&rctl_lock); + RCTL_WLOCK(); while (!LIST_EMPTY(&racct->r_rule_links)) { link = LIST_FIRST(&racct->r_rule_links); LIST_REMOVE(link, rrl_next); rctl_rule_release(link->rrl_rule); uma_zfree(rctl_rule_link_zone, link); } - rw_wunlock(&rctl_lock); + RCTL_WUNLOCK(); } static void rctl_init(void) { if (!racct_enable) return; rctl_rule_link_zone = uma_zcreate("rctl_rule_link", sizeof(struct rctl_rule_link), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE); rctl_rule_zone = uma_zcreate("rctl_rule", sizeof(struct rctl_rule), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE); } #else /* !RCTL */ int sys_rctl_get_racct(struct thread *td, struct rctl_get_racct_args *uap) { return (ENOSYS); } int sys_rctl_get_rules(struct thread *td, struct rctl_get_rules_args *uap) { return (ENOSYS); } int sys_rctl_get_limits(struct thread *td, struct rctl_get_limits_args *uap) { return (ENOSYS); } int sys_rctl_add_rule(struct thread *td, struct rctl_add_rule_args *uap) { return (ENOSYS); } int sys_rctl_remove_rule(struct thread *td, struct rctl_remove_rule_args *uap) { return (ENOSYS); } #endif /* !RCTL */ Index: projects/release-pkg/sys/kern/subr_smp.c =================================================================== --- projects/release-pkg/sys/kern/subr_smp.c (revision 297604) +++ projects/release-pkg/sys/kern/subr_smp.c (revision 297605) @@ -1,1109 +1,1140 @@ /*- * Copyright (c) 2001, John Baldwin . * 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. */ /* * This module holds the global variables and machine independent functions * used for the kernel SMP support. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_sched.h" #ifdef SMP MALLOC_DEFINE(M_TOPO, "toponodes", "SMP topology data"); -#endif -#ifdef SMP volatile cpuset_t stopped_cpus; volatile cpuset_t started_cpus; volatile cpuset_t suspended_cpus; cpuset_t hlt_cpus_mask; cpuset_t logical_cpus_mask; void (*cpustop_restartfunc)(void); #endif static int sysctl_kern_smp_active(SYSCTL_HANDLER_ARGS); /* This is used in modules that need to work in both SMP and UP. */ cpuset_t all_cpus; int mp_ncpus; /* export this for libkvm consumers. */ int mp_maxcpus = MAXCPU; volatile int smp_started; u_int mp_maxid; static SYSCTL_NODE(_kern, OID_AUTO, smp, CTLFLAG_RD|CTLFLAG_CAPRD, NULL, "Kernel SMP"); SYSCTL_INT(_kern_smp, OID_AUTO, maxid, CTLFLAG_RD|CTLFLAG_CAPRD, &mp_maxid, 0, "Max CPU ID."); SYSCTL_INT(_kern_smp, OID_AUTO, maxcpus, CTLFLAG_RD|CTLFLAG_CAPRD, &mp_maxcpus, 0, "Max number of CPUs that the system was compiled for."); SYSCTL_PROC(_kern_smp, OID_AUTO, active, CTLFLAG_RD | CTLTYPE_INT, NULL, 0, sysctl_kern_smp_active, "I", "Indicates system is running in SMP mode"); int smp_disabled = 0; /* has smp been disabled? */ SYSCTL_INT(_kern_smp, OID_AUTO, disabled, CTLFLAG_RDTUN|CTLFLAG_CAPRD, &smp_disabled, 0, "SMP has been disabled from the loader"); int smp_cpus = 1; /* how many cpu's running */ SYSCTL_INT(_kern_smp, OID_AUTO, cpus, CTLFLAG_RD|CTLFLAG_CAPRD, &smp_cpus, 0, "Number of CPUs online"); int smp_topology = 0; /* Which topology we're using. */ SYSCTL_INT(_kern_smp, OID_AUTO, topology, CTLFLAG_RDTUN, &smp_topology, 0, "Topology override setting; 0 is default provided by hardware."); #ifdef SMP /* Enable forwarding of a signal to a process running on a different CPU */ static int forward_signal_enabled = 1; SYSCTL_INT(_kern_smp, OID_AUTO, forward_signal_enabled, CTLFLAG_RW, &forward_signal_enabled, 0, "Forwarding of a signal to a process on a different CPU"); /* Variables needed for SMP rendezvous. */ static volatile int smp_rv_ncpus; static void (*volatile smp_rv_setup_func)(void *arg); static void (*volatile smp_rv_action_func)(void *arg); static void (*volatile smp_rv_teardown_func)(void *arg); static void *volatile smp_rv_func_arg; static volatile int smp_rv_waiters[4]; /* * Shared mutex to restrict busywaits between smp_rendezvous() and * smp(_targeted)_tlb_shootdown(). A deadlock occurs if both of these * functions trigger at once and cause multiple CPUs to busywait with * interrupts disabled. */ struct mtx smp_ipi_mtx; /* * Let the MD SMP code initialize mp_maxid very early if it can. */ static void mp_setmaxid(void *dummy) { cpu_mp_setmaxid(); KASSERT(mp_ncpus >= 1, ("%s: CPU count < 1", __func__)); KASSERT(mp_ncpus > 1 || mp_maxid == 0, ("%s: one CPU but mp_maxid is not zero", __func__)); KASSERT(mp_maxid >= mp_ncpus - 1, ("%s: counters out of sync: max %d, count %d", __func__, mp_maxid, mp_ncpus)); } SYSINIT(cpu_mp_setmaxid, SI_SUB_TUNABLES, SI_ORDER_FIRST, mp_setmaxid, NULL); /* * Call the MD SMP initialization code. */ static void mp_start(void *dummy) { mtx_init(&smp_ipi_mtx, "smp rendezvous", NULL, MTX_SPIN); /* Probe for MP hardware. */ if (smp_disabled != 0 || cpu_mp_probe() == 0) { mp_ncpus = 1; CPU_SETOF(PCPU_GET(cpuid), &all_cpus); return; } cpu_mp_start(); printf("FreeBSD/SMP: Multiprocessor System Detected: %d CPUs\n", mp_ncpus); cpu_mp_announce(); } SYSINIT(cpu_mp, SI_SUB_CPU, SI_ORDER_THIRD, mp_start, NULL); void forward_signal(struct thread *td) { int id; /* * signotify() has already set TDF_ASTPENDING and TDF_NEEDSIGCHECK on * this thread, so all we need to do is poke it if it is currently * executing so that it executes ast(). */ THREAD_LOCK_ASSERT(td, MA_OWNED); KASSERT(TD_IS_RUNNING(td), ("forward_signal: thread is not TDS_RUNNING")); CTR1(KTR_SMP, "forward_signal(%p)", td->td_proc); if (!smp_started || cold || panicstr) return; if (!forward_signal_enabled) return; /* No need to IPI ourself. */ if (td == curthread) return; id = td->td_oncpu; if (id == NOCPU) return; ipi_cpu(id, IPI_AST); } /* * When called the executing CPU will send an IPI to all other CPUs * requesting that they halt execution. * * Usually (but not necessarily) called with 'other_cpus' as its arg. * * - Signals all CPUs in map to stop. * - Waits for each to stop. * * Returns: * -1: error * 0: NA * 1: ok * */ static int generic_stop_cpus(cpuset_t map, u_int type) { #ifdef KTR char cpusetbuf[CPUSETBUFSIZ]; #endif static volatile u_int stopping_cpu = NOCPU; int i; volatile cpuset_t *cpus; KASSERT( #if defined(__amd64__) || defined(__i386__) type == IPI_STOP || type == IPI_STOP_HARD || type == IPI_SUSPEND, #else type == IPI_STOP || type == IPI_STOP_HARD, #endif ("%s: invalid stop type", __func__)); if (!smp_started) return (0); CTR2(KTR_SMP, "stop_cpus(%s) with %u type", cpusetobj_strprint(cpusetbuf, &map), type); #if defined(__amd64__) || defined(__i386__) /* * When suspending, ensure there are are no IPIs in progress. * IPIs that have been issued, but not yet delivered (e.g. * not pending on a vCPU when running under virtualization) * will be lost, violating FreeBSD's assumption of reliable * IPI delivery. */ if (type == IPI_SUSPEND) mtx_lock_spin(&smp_ipi_mtx); #endif if (stopping_cpu != PCPU_GET(cpuid)) while (atomic_cmpset_int(&stopping_cpu, NOCPU, PCPU_GET(cpuid)) == 0) while (stopping_cpu != NOCPU) cpu_spinwait(); /* spin */ /* send the stop IPI to all CPUs in map */ ipi_selected(map, type); #if defined(__amd64__) || defined(__i386__) if (type == IPI_SUSPEND) cpus = &suspended_cpus; else #endif cpus = &stopped_cpus; i = 0; while (!CPU_SUBSET(cpus, &map)) { /* spin */ cpu_spinwait(); i++; if (i == 100000000) { printf("timeout stopping cpus\n"); break; } } #if defined(__amd64__) || defined(__i386__) if (type == IPI_SUSPEND) mtx_unlock_spin(&smp_ipi_mtx); #endif stopping_cpu = NOCPU; return (1); } int stop_cpus(cpuset_t map) { return (generic_stop_cpus(map, IPI_STOP)); } int stop_cpus_hard(cpuset_t map) { return (generic_stop_cpus(map, IPI_STOP_HARD)); } #if defined(__amd64__) || defined(__i386__) int suspend_cpus(cpuset_t map) { return (generic_stop_cpus(map, IPI_SUSPEND)); } #endif /* * Called by a CPU to restart stopped CPUs. * * Usually (but not necessarily) called with 'stopped_cpus' as its arg. * * - Signals all CPUs in map to restart. * - Waits for each to restart. * * Returns: * -1: error * 0: NA * 1: ok */ static int generic_restart_cpus(cpuset_t map, u_int type) { #ifdef KTR char cpusetbuf[CPUSETBUFSIZ]; #endif volatile cpuset_t *cpus; KASSERT( #if defined(__amd64__) || defined(__i386__) type == IPI_STOP || type == IPI_STOP_HARD || type == IPI_SUSPEND, #else type == IPI_STOP || type == IPI_STOP_HARD, #endif ("%s: invalid stop type", __func__)); if (!smp_started) return 0; CTR1(KTR_SMP, "restart_cpus(%s)", cpusetobj_strprint(cpusetbuf, &map)); #if defined(__amd64__) || defined(__i386__) if (type == IPI_SUSPEND) cpus = &suspended_cpus; else #endif cpus = &stopped_cpus; /* signal other cpus to restart */ CPU_COPY_STORE_REL(&map, &started_cpus); /* wait for each to clear its bit */ while (CPU_OVERLAP(cpus, &map)) cpu_spinwait(); return 1; } int restart_cpus(cpuset_t map) { return (generic_restart_cpus(map, IPI_STOP)); } #if defined(__amd64__) || defined(__i386__) int resume_cpus(cpuset_t map) { return (generic_restart_cpus(map, IPI_SUSPEND)); } #endif /* * All-CPU rendezvous. CPUs are signalled, all execute the setup function * (if specified), rendezvous, execute the action function (if specified), * rendezvous again, execute the teardown function (if specified), and then * resume. * * Note that the supplied external functions _must_ be reentrant and aware * that they are running in parallel and in an unknown lock context. */ void smp_rendezvous_action(void) { struct thread *td; void *local_func_arg; void (*local_setup_func)(void*); void (*local_action_func)(void*); void (*local_teardown_func)(void*); #ifdef INVARIANTS int owepreempt; #endif /* Ensure we have up-to-date values. */ atomic_add_acq_int(&smp_rv_waiters[0], 1); while (smp_rv_waiters[0] < smp_rv_ncpus) cpu_spinwait(); /* Fetch rendezvous parameters after acquire barrier. */ local_func_arg = smp_rv_func_arg; local_setup_func = smp_rv_setup_func; local_action_func = smp_rv_action_func; local_teardown_func = smp_rv_teardown_func; /* * Use a nested critical section to prevent any preemptions * from occurring during a rendezvous action routine. * Specifically, if a rendezvous handler is invoked via an IPI * and the interrupted thread was in the critical_exit() * function after setting td_critnest to 0 but before * performing a deferred preemption, this routine can be * invoked with td_critnest set to 0 and td_owepreempt true. * In that case, a critical_exit() during the rendezvous * action would trigger a preemption which is not permitted in * a rendezvous action. To fix this, wrap all of the * rendezvous action handlers in a critical section. We * cannot use a regular critical section however as having * critical_exit() preempt from this routine would also be * problematic (the preemption must not occur before the IPI * has been acknowledged via an EOI). Instead, we * intentionally ignore td_owepreempt when leaving the * critical section. This should be harmless because we do * not permit rendezvous action routines to schedule threads, * and thus td_owepreempt should never transition from 0 to 1 * during this routine. */ td = curthread; td->td_critnest++; #ifdef INVARIANTS owepreempt = td->td_owepreempt; #endif /* * If requested, run a setup function before the main action * function. Ensure all CPUs have completed the setup * function before moving on to the action function. */ if (local_setup_func != smp_no_rendevous_barrier) { if (smp_rv_setup_func != NULL) smp_rv_setup_func(smp_rv_func_arg); atomic_add_int(&smp_rv_waiters[1], 1); while (smp_rv_waiters[1] < smp_rv_ncpus) cpu_spinwait(); } if (local_action_func != NULL) local_action_func(local_func_arg); if (local_teardown_func != smp_no_rendevous_barrier) { /* * Signal that the main action has been completed. If a * full exit rendezvous is requested, then all CPUs will * wait here until all CPUs have finished the main action. */ atomic_add_int(&smp_rv_waiters[2], 1); while (smp_rv_waiters[2] < smp_rv_ncpus) cpu_spinwait(); if (local_teardown_func != NULL) local_teardown_func(local_func_arg); } /* * Signal that the rendezvous is fully completed by this CPU. * This means that no member of smp_rv_* pseudo-structure will be * accessed by this target CPU after this point; in particular, * memory pointed by smp_rv_func_arg. * * The release semantic ensures that all accesses performed by * the current CPU are visible when smp_rendezvous_cpus() * returns, by synchronizing with the * atomic_load_acq_int(&smp_rv_waiters[3]). */ atomic_add_rel_int(&smp_rv_waiters[3], 1); td->td_critnest--; KASSERT(owepreempt == td->td_owepreempt, ("rendezvous action changed td_owepreempt")); } void smp_rendezvous_cpus(cpuset_t map, void (* setup_func)(void *), void (* action_func)(void *), void (* teardown_func)(void *), void *arg) { int curcpumap, i, ncpus = 0; /* Look comments in the !SMP case. */ if (!smp_started) { spinlock_enter(); if (setup_func != NULL) setup_func(arg); if (action_func != NULL) action_func(arg); if (teardown_func != NULL) teardown_func(arg); spinlock_exit(); return; } CPU_FOREACH(i) { if (CPU_ISSET(i, &map)) ncpus++; } if (ncpus == 0) panic("ncpus is 0 with non-zero map"); mtx_lock_spin(&smp_ipi_mtx); /* Pass rendezvous parameters via global variables. */ smp_rv_ncpus = ncpus; smp_rv_setup_func = setup_func; smp_rv_action_func = action_func; smp_rv_teardown_func = teardown_func; smp_rv_func_arg = arg; smp_rv_waiters[1] = 0; smp_rv_waiters[2] = 0; smp_rv_waiters[3] = 0; atomic_store_rel_int(&smp_rv_waiters[0], 0); /* * Signal other processors, which will enter the IPI with * interrupts off. */ curcpumap = CPU_ISSET(curcpu, &map); CPU_CLR(curcpu, &map); ipi_selected(map, IPI_RENDEZVOUS); /* Check if the current CPU is in the map */ if (curcpumap != 0) smp_rendezvous_action(); /* * Ensure that the master CPU waits for all the other * CPUs to finish the rendezvous, so that smp_rv_* * pseudo-structure and the arg are guaranteed to not * be in use. * * Load acquire synchronizes with the release add in * smp_rendezvous_action(), which ensures that our caller sees * all memory actions done by the called functions on other * CPUs. */ while (atomic_load_acq_int(&smp_rv_waiters[3]) < ncpus) cpu_spinwait(); mtx_unlock_spin(&smp_ipi_mtx); } void smp_rendezvous(void (* setup_func)(void *), void (* action_func)(void *), void (* teardown_func)(void *), void *arg) { smp_rendezvous_cpus(all_cpus, setup_func, action_func, teardown_func, arg); } static struct cpu_group group[MAXCPU * MAX_CACHE_LEVELS + 1]; struct cpu_group * smp_topo(void) { char cpusetbuf[CPUSETBUFSIZ], cpusetbuf2[CPUSETBUFSIZ]; struct cpu_group *top; /* * Check for a fake topology request for debugging purposes. */ switch (smp_topology) { case 1: /* Dual core with no sharing. */ top = smp_topo_1level(CG_SHARE_NONE, 2, 0); break; case 2: /* No topology, all cpus are equal. */ top = smp_topo_none(); break; case 3: /* Dual core with shared L2. */ top = smp_topo_1level(CG_SHARE_L2, 2, 0); break; case 4: /* quad core, shared l3 among each package, private l2. */ top = smp_topo_1level(CG_SHARE_L3, 4, 0); break; case 5: /* quad core, 2 dualcore parts on each package share l2. */ top = smp_topo_2level(CG_SHARE_NONE, 2, CG_SHARE_L2, 2, 0); break; case 6: /* Single-core 2xHTT */ top = smp_topo_1level(CG_SHARE_L1, 2, CG_FLAG_HTT); break; case 7: /* quad core with a shared l3, 8 threads sharing L2. */ top = smp_topo_2level(CG_SHARE_L3, 4, CG_SHARE_L2, 8, CG_FLAG_SMT); break; default: /* Default, ask the system what it wants. */ top = cpu_topo(); break; } /* * Verify the returned topology. */ if (top->cg_count != mp_ncpus) panic("Built bad topology at %p. CPU count %d != %d", top, top->cg_count, mp_ncpus); if (CPU_CMP(&top->cg_mask, &all_cpus)) panic("Built bad topology at %p. CPU mask (%s) != (%s)", top, cpusetobj_strprint(cpusetbuf, &top->cg_mask), cpusetobj_strprint(cpusetbuf2, &all_cpus)); return (top); } struct cpu_group * smp_topo_alloc(u_int count) { static u_int index; u_int curr; curr = index; index += count; return (&group[curr]); } struct cpu_group * smp_topo_none(void) { struct cpu_group *top; top = &group[0]; top->cg_parent = NULL; top->cg_child = NULL; top->cg_mask = all_cpus; top->cg_count = mp_ncpus; top->cg_children = 0; top->cg_level = CG_SHARE_NONE; top->cg_flags = 0; return (top); } static int smp_topo_addleaf(struct cpu_group *parent, struct cpu_group *child, int share, int count, int flags, int start) { char cpusetbuf[CPUSETBUFSIZ], cpusetbuf2[CPUSETBUFSIZ]; cpuset_t mask; int i; CPU_ZERO(&mask); for (i = 0; i < count; i++, start++) CPU_SET(start, &mask); child->cg_parent = parent; child->cg_child = NULL; child->cg_children = 0; child->cg_level = share; child->cg_count = count; child->cg_flags = flags; child->cg_mask = mask; parent->cg_children++; for (; parent != NULL; parent = parent->cg_parent) { if (CPU_OVERLAP(&parent->cg_mask, &child->cg_mask)) panic("Duplicate children in %p. mask (%s) child (%s)", parent, cpusetobj_strprint(cpusetbuf, &parent->cg_mask), cpusetobj_strprint(cpusetbuf2, &child->cg_mask)); CPU_OR(&parent->cg_mask, &child->cg_mask); parent->cg_count += child->cg_count; } return (start); } struct cpu_group * smp_topo_1level(int share, int count, int flags) { struct cpu_group *child; struct cpu_group *top; int packages; int cpu; int i; cpu = 0; top = &group[0]; packages = mp_ncpus / count; top->cg_child = child = &group[1]; top->cg_level = CG_SHARE_NONE; for (i = 0; i < packages; i++, child++) cpu = smp_topo_addleaf(top, child, share, count, flags, cpu); return (top); } struct cpu_group * smp_topo_2level(int l2share, int l2count, int l1share, int l1count, int l1flags) { struct cpu_group *top; struct cpu_group *l1g; struct cpu_group *l2g; int cpu; int i; int j; cpu = 0; top = &group[0]; l2g = &group[1]; top->cg_child = l2g; top->cg_level = CG_SHARE_NONE; top->cg_children = mp_ncpus / (l2count * l1count); l1g = l2g + top->cg_children; for (i = 0; i < top->cg_children; i++, l2g++) { l2g->cg_parent = top; l2g->cg_child = l1g; l2g->cg_level = l2share; for (j = 0; j < l2count; j++, l1g++) cpu = smp_topo_addleaf(l2g, l1g, l1share, l1count, l1flags, cpu); } return (top); } struct cpu_group * smp_topo_find(struct cpu_group *top, int cpu) { struct cpu_group *cg; cpuset_t mask; int children; int i; CPU_SETOF(cpu, &mask); cg = top; for (;;) { if (!CPU_OVERLAP(&cg->cg_mask, &mask)) return (NULL); if (cg->cg_children == 0) return (cg); children = cg->cg_children; for (i = 0, cg = cg->cg_child; i < children; cg++, i++) if (CPU_OVERLAP(&cg->cg_mask, &mask)) break; } return (NULL); } #else /* !SMP */ void smp_rendezvous_cpus(cpuset_t map, void (*setup_func)(void *), void (*action_func)(void *), void (*teardown_func)(void *), void *arg) { /* * In the !SMP case we just need to ensure the same initial conditions * as the SMP case. */ spinlock_enter(); if (setup_func != NULL) setup_func(arg); if (action_func != NULL) action_func(arg); if (teardown_func != NULL) teardown_func(arg); spinlock_exit(); } void smp_rendezvous(void (*setup_func)(void *), void (*action_func)(void *), void (*teardown_func)(void *), void *arg) { /* Look comments in the smp_rendezvous_cpus() case. */ spinlock_enter(); if (setup_func != NULL) setup_func(arg); if (action_func != NULL) action_func(arg); if (teardown_func != NULL) teardown_func(arg); spinlock_exit(); } /* * Provide dummy SMP support for UP kernels. Modules that need to use SMP * APIs will still work using this dummy support. */ static void mp_setvariables_for_up(void *dummy) { mp_ncpus = 1; mp_maxid = PCPU_GET(cpuid); CPU_SETOF(mp_maxid, &all_cpus); KASSERT(PCPU_GET(cpuid) == 0, ("UP must have a CPU ID of zero")); } SYSINIT(cpu_mp_setvariables, SI_SUB_TUNABLES, SI_ORDER_FIRST, mp_setvariables_for_up, NULL); #endif /* SMP */ void smp_no_rendevous_barrier(void *dummy) { #ifdef SMP KASSERT((!smp_started),("smp_no_rendevous called and smp is started")); #endif } /* * Wait specified idle threads to switch once. This ensures that even * preempted threads have cycled through the switch function once, * exiting their codepaths. This allows us to change global pointers * with no other synchronization. */ int quiesce_cpus(cpuset_t map, const char *wmesg, int prio) { struct pcpu *pcpu; u_int gen[MAXCPU]; int error; int cpu; error = 0; for (cpu = 0; cpu <= mp_maxid; cpu++) { if (!CPU_ISSET(cpu, &map) || CPU_ABSENT(cpu)) continue; pcpu = pcpu_find(cpu); gen[cpu] = pcpu->pc_idlethread->td_generation; } for (cpu = 0; cpu <= mp_maxid; cpu++) { if (!CPU_ISSET(cpu, &map) || CPU_ABSENT(cpu)) continue; pcpu = pcpu_find(cpu); thread_lock(curthread); sched_bind(curthread, cpu); thread_unlock(curthread); while (gen[cpu] == pcpu->pc_idlethread->td_generation) { error = tsleep(quiesce_cpus, prio, wmesg, 1); if (error != EWOULDBLOCK) goto out; error = 0; } } out: thread_lock(curthread); sched_unbind(curthread); thread_unlock(curthread); return (error); } int quiesce_all_cpus(const char *wmesg, int prio) { return quiesce_cpus(all_cpus, wmesg, prio); } /* Extra care is taken with this sysctl because the data type is volatile */ static int sysctl_kern_smp_active(SYSCTL_HANDLER_ARGS) { int error, active; active = smp_started; error = SYSCTL_OUT(req, &active, sizeof(active)); return (error); } #ifdef SMP void topo_init_node(struct topo_node *node) { bzero(node, sizeof(*node)); TAILQ_INIT(&node->children); } void topo_init_root(struct topo_node *root) { topo_init_node(root); root->type = TOPO_TYPE_SYSTEM; } +/* + * Add a child node with the given ID under the given parent. + * Do nothing if there is already a child with that ID. + */ struct topo_node * topo_add_node_by_hwid(struct topo_node *parent, int hwid, topo_node_type type, uintptr_t subtype) { struct topo_node *node; TAILQ_FOREACH_REVERSE(node, &parent->children, topo_children, siblings) { if (node->hwid == hwid && node->type == type && node->subtype == subtype) { return (node); } } node = malloc(sizeof(*node), M_TOPO, M_WAITOK); topo_init_node(node); node->parent = parent; node->hwid = hwid; node->type = type; node->subtype = subtype; TAILQ_INSERT_TAIL(&parent->children, node, siblings); parent->nchildren++; return (node); } +/* + * Find a child node with the given ID under the given parent. + */ struct topo_node * topo_find_node_by_hwid(struct topo_node *parent, int hwid, topo_node_type type, uintptr_t subtype) { struct topo_node *node; TAILQ_FOREACH(node, &parent->children, siblings) { if (node->hwid == hwid && node->type == type && node->subtype == subtype) { return (node); } } return (NULL); } +/* + * Given a node change the order of its parent's child nodes such + * that the node becomes the firt child while preserving the cyclic + * order of the children. In other words, the given node is promoted + * by rotation. + */ void topo_promote_child(struct topo_node *child) { struct topo_node *next; struct topo_node *node; struct topo_node *parent; parent = child->parent; next = TAILQ_NEXT(child, siblings); TAILQ_REMOVE(&parent->children, child, siblings); TAILQ_INSERT_HEAD(&parent->children, child, siblings); while (next != NULL) { node = next; next = TAILQ_NEXT(node, siblings); TAILQ_REMOVE(&parent->children, node, siblings); TAILQ_INSERT_AFTER(&parent->children, child, node, siblings); child = node; } } +/* + * Iterate to the next node in the depth-first search (traversal) of + * the topology tree. + */ struct topo_node * topo_next_node(struct topo_node *top, struct topo_node *node) { struct topo_node *next; if ((next = TAILQ_FIRST(&node->children)) != NULL) return (next); if ((next = TAILQ_NEXT(node, siblings)) != NULL) return (next); while ((node = node->parent) != top) if ((next = TAILQ_NEXT(node, siblings)) != NULL) return (next); return (NULL); } +/* + * Iterate to the next node in the depth-first search of the topology tree, + * but without descending below the current node. + */ struct topo_node * topo_next_nonchild_node(struct topo_node *top, struct topo_node *node) { struct topo_node *next; if ((next = TAILQ_NEXT(node, siblings)) != NULL) return (next); while ((node = node->parent) != top) if ((next = TAILQ_NEXT(node, siblings)) != NULL) return (next); return (NULL); } +/* + * Assign the given ID to the given topology node that represents a logical + * processor. + */ void topo_set_pu_id(struct topo_node *node, cpuid_t id) { KASSERT(node->type == TOPO_TYPE_PU, ("topo_set_pu_id: wrong node type: %u", node->type)); KASSERT(CPU_EMPTY(&node->cpuset) && node->cpu_count == 0, ("topo_set_pu_id: cpuset already not empty")); node->id = id; CPU_SET(id, &node->cpuset); node->cpu_count = 1; node->subtype = 1; while ((node = node->parent) != NULL) { if (CPU_ISSET(id, &node->cpuset)) break; CPU_SET(id, &node->cpuset); node->cpu_count++; } } +/* + * Check if the topology is uniform, that is, each package has the same number + * of cores in it and each core has the same number of threads (logical + * processors) in it. If so, calculate the number of package, the number of + * cores per package and the number of logical processors per core. + * 'all' parameter tells whether to include administratively disabled logical + * processors into the analysis. + */ int topo_analyze(struct topo_node *topo_root, int all, int *pkg_count, int *cores_per_pkg, int *thrs_per_core) { struct topo_node *pkg_node; struct topo_node *core_node; struct topo_node *pu_node; int thrs_per_pkg; int cpp_counter; int tpc_counter; int tpp_counter; *pkg_count = 0; *cores_per_pkg = -1; *thrs_per_core = -1; thrs_per_pkg = -1; pkg_node = topo_root; while (pkg_node != NULL) { if (pkg_node->type != TOPO_TYPE_PKG) { pkg_node = topo_next_node(topo_root, pkg_node); continue; } if (!all && CPU_EMPTY(&pkg_node->cpuset)) { pkg_node = topo_next_nonchild_node(topo_root, pkg_node); continue; } (*pkg_count)++; cpp_counter = 0; tpp_counter = 0; core_node = pkg_node; while (core_node != NULL) { if (core_node->type == TOPO_TYPE_CORE) { if (!all && CPU_EMPTY(&core_node->cpuset)) { core_node = topo_next_nonchild_node(pkg_node, core_node); continue; } cpp_counter++; tpc_counter = 0; pu_node = core_node; while (pu_node != NULL) { if (pu_node->type == TOPO_TYPE_PU && (all || !CPU_EMPTY(&pu_node->cpuset))) tpc_counter++; pu_node = topo_next_node(core_node, pu_node); } if (*thrs_per_core == -1) *thrs_per_core = tpc_counter; else if (*thrs_per_core != tpc_counter) return (0); core_node = topo_next_nonchild_node(pkg_node, core_node); } else { /* PU node directly under PKG. */ if (core_node->type == TOPO_TYPE_PU && (all || !CPU_EMPTY(&core_node->cpuset))) tpp_counter++; core_node = topo_next_node(pkg_node, core_node); } } if (*cores_per_pkg == -1) *cores_per_pkg = cpp_counter; else if (*cores_per_pkg != cpp_counter) return (0); if (thrs_per_pkg == -1) thrs_per_pkg = tpp_counter; else if (thrs_per_pkg != tpp_counter) return (0); pkg_node = topo_next_nonchild_node(topo_root, pkg_node); } KASSERT(*pkg_count > 0, ("bug in topology or analysis")); if (*cores_per_pkg == 0) { KASSERT(*thrs_per_core == -1 && thrs_per_pkg > 0, ("bug in topology or analysis")); *thrs_per_core = thrs_per_pkg; } return (1); } #endif /* SMP */ Index: projects/release-pkg/sys/net/netisr.c =================================================================== --- projects/release-pkg/sys/net/netisr.c (revision 297604) +++ projects/release-pkg/sys/net/netisr.c (revision 297605) @@ -1,1375 +1,1372 @@ /*- * Copyright (c) 2007-2009 Robert N. M. Watson * Copyright (c) 2010-2011 Juniper Networks, Inc. * All rights reserved. * * This software was developed by Robert N. M. Watson under contract * to Juniper Networks, Inc. * * 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$"); /* * netisr is a packet dispatch service, allowing synchronous (directly * dispatched) and asynchronous (deferred dispatch) processing of packets by * registered protocol handlers. Callers pass a protocol identifier and * packet to netisr, along with a direct dispatch hint, and work will either * be immediately processed by the registered handler, or passed to a * software interrupt (SWI) thread for deferred dispatch. Callers will * generally select one or the other based on: * * - Whether directly dispatching a netisr handler lead to code reentrance or * lock recursion, such as entering the socket code from the socket code. * - Whether directly dispatching a netisr handler lead to recursive * processing, such as when decapsulating several wrapped layers of tunnel * information (IPSEC within IPSEC within ...). * * Maintaining ordering for protocol streams is a critical design concern. * Enforcing ordering limits the opportunity for concurrency, but maintains * the strong ordering requirements found in some protocols, such as TCP. Of * related concern is CPU affinity--it is desirable to process all data * associated with a particular stream on the same CPU over time in order to * avoid acquiring locks associated with the connection on different CPUs, * keep connection data in one cache, and to generally encourage associated * user threads to live on the same CPU as the stream. It's also desirable * to avoid lock migration and contention where locks are associated with * more than one flow. * * netisr supports several policy variations, represented by the * NETISR_POLICY_* constants, allowing protocols to play various roles in * identifying flows, assigning work to CPUs, etc. These are described in * netisr.h. */ #include "opt_ddb.h" #include "opt_device_polling.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DDB #include #endif #define _WANT_NETISR_INTERNAL /* Enable definitions from netisr_internal.h */ #include #include #include #include #include /*- * Synchronize use and modification of the registered netisr data structures; * acquire a read lock while modifying the set of registered protocols to * prevent partially registered or unregistered protocols from being run. * * The following data structures and fields are protected by this lock: * * - The netisr_proto array, including all fields of struct netisr_proto. * - The nws array, including all fields of struct netisr_worker. * - The nws_array array. * * Note: the NETISR_LOCKING define controls whether read locks are acquired * in packet processing paths requiring netisr registration stability. This * is disabled by default as it can lead to measurable performance * degradation even with rmlocks (3%-6% for loopback ping-pong traffic), and * because netisr registration and unregistration is extremely rare at * runtime. If it becomes more common, this decision should be revisited. * * XXXRW: rmlocks don't support assertions. */ static struct rmlock netisr_rmlock; #define NETISR_LOCK_INIT() rm_init_flags(&netisr_rmlock, "netisr", \ RM_NOWITNESS) #define NETISR_LOCK_ASSERT() #define NETISR_RLOCK(tracker) rm_rlock(&netisr_rmlock, (tracker)) #define NETISR_RUNLOCK(tracker) rm_runlock(&netisr_rmlock, (tracker)) #define NETISR_WLOCK() rm_wlock(&netisr_rmlock) #define NETISR_WUNLOCK() rm_wunlock(&netisr_rmlock) /* #define NETISR_LOCKING */ static SYSCTL_NODE(_net, OID_AUTO, isr, CTLFLAG_RW, 0, "netisr"); /*- * Three global direct dispatch policies are supported: * * NETISR_DISPATCH_DEFERRED: All work is deferred for a netisr, regardless of * context (may be overriden by protocols). * * NETISR_DISPATCH_HYBRID: If the executing context allows direct dispatch, * and we're running on the CPU the work would be performed on, then direct * dispatch it if it wouldn't violate ordering constraints on the workstream. * * NETISR_DISPATCH_DIRECT: If the executing context allows direct dispatch, * always direct dispatch. (The default.) * * Notice that changing the global policy could lead to short periods of * misordered processing, but this is considered acceptable as compared to * the complexity of enforcing ordering during policy changes. Protocols can * override the global policy (when they're not doing that, they select * NETISR_DISPATCH_DEFAULT). */ #define NETISR_DISPATCH_POLICY_DEFAULT NETISR_DISPATCH_DIRECT #define NETISR_DISPATCH_POLICY_MAXSTR 20 /* Used for temporary buffers. */ static u_int netisr_dispatch_policy = NETISR_DISPATCH_POLICY_DEFAULT; static int sysctl_netisr_dispatch_policy(SYSCTL_HANDLER_ARGS); SYSCTL_PROC(_net_isr, OID_AUTO, dispatch, CTLTYPE_STRING | CTLFLAG_RWTUN, 0, 0, sysctl_netisr_dispatch_policy, "A", "netisr dispatch policy"); /* * Allow the administrator to limit the number of threads (CPUs) to use for * netisr. We don't check netisr_maxthreads before creating the thread for * CPU 0. This must be set at boot. We will create at most one thread per CPU. * By default we initialize this to 1 which would assign just 1 cpu (cpu0) and * therefore only 1 workstream. If set to -1, netisr would use all cpus * (mp_ncpus) and therefore would have those many workstreams. One workstream * per thread (CPU). */ static int netisr_maxthreads = 1; /* Max number of threads. */ SYSCTL_INT(_net_isr, OID_AUTO, maxthreads, CTLFLAG_RDTUN, &netisr_maxthreads, 0, "Use at most this many CPUs for netisr processing"); static int netisr_bindthreads = 0; /* Bind threads to CPUs. */ SYSCTL_INT(_net_isr, OID_AUTO, bindthreads, CTLFLAG_RDTUN, &netisr_bindthreads, 0, "Bind netisr threads to CPUs."); /* * Limit per-workstream mbuf queue limits s to at most net.isr.maxqlimit, * both for initial configuration and later modification using * netisr_setqlimit(). */ #define NETISR_DEFAULT_MAXQLIMIT 10240 static u_int netisr_maxqlimit = NETISR_DEFAULT_MAXQLIMIT; SYSCTL_UINT(_net_isr, OID_AUTO, maxqlimit, CTLFLAG_RDTUN, &netisr_maxqlimit, 0, "Maximum netisr per-protocol, per-CPU queue depth."); /* * The default per-workstream mbuf queue limit for protocols that don't * initialize the nh_qlimit field of their struct netisr_handler. If this is * set above netisr_maxqlimit, we truncate it to the maximum during boot. */ #define NETISR_DEFAULT_DEFAULTQLIMIT 256 static u_int netisr_defaultqlimit = NETISR_DEFAULT_DEFAULTQLIMIT; SYSCTL_UINT(_net_isr, OID_AUTO, defaultqlimit, CTLFLAG_RDTUN, &netisr_defaultqlimit, 0, "Default netisr per-protocol, per-CPU queue limit if not set by protocol"); /* * Store and export the compile-time constant NETISR_MAXPROT limit on the * number of protocols that can register with netisr at a time. This is * required for crashdump analysis, as it sizes netisr_proto[]. */ static u_int netisr_maxprot = NETISR_MAXPROT; SYSCTL_UINT(_net_isr, OID_AUTO, maxprot, CTLFLAG_RD, &netisr_maxprot, 0, "Compile-time limit on the number of protocols supported by netisr."); /* * The netisr_proto array describes all registered protocols, indexed by * protocol number. See netisr_internal.h for more details. */ static struct netisr_proto netisr_proto[NETISR_MAXPROT]; /* * Per-CPU workstream data. See netisr_internal.h for more details. */ DPCPU_DEFINE(struct netisr_workstream, nws); /* * Map contiguous values between 0 and nws_count into CPU IDs appropriate for * accessing workstreams. This allows constructions of the form * DPCPU_ID_GET(nws_array[arbitraryvalue % nws_count], nws). */ static u_int nws_array[MAXCPU]; /* * Number of registered workstreams. Will be at most the number of running * CPUs once fully started. */ static u_int nws_count; SYSCTL_UINT(_net_isr, OID_AUTO, numthreads, CTLFLAG_RD, &nws_count, 0, "Number of extant netisr threads."); /* * Synchronization for each workstream: a mutex protects all mutable fields * in each stream, including per-protocol state (mbuf queues). The SWI is * woken up if asynchronous dispatch is required. */ #define NWS_LOCK(s) mtx_lock(&(s)->nws_mtx) #define NWS_LOCK_ASSERT(s) mtx_assert(&(s)->nws_mtx, MA_OWNED) #define NWS_UNLOCK(s) mtx_unlock(&(s)->nws_mtx) #define NWS_SIGNAL(s) swi_sched((s)->nws_swi_cookie, 0) /* * Utility routines for protocols that implement their own mapping of flows * to CPUs. */ u_int netisr_get_cpucount(void) { return (nws_count); } u_int netisr_get_cpuid(u_int cpunumber) { KASSERT(cpunumber < nws_count, ("%s: %u > %u", __func__, cpunumber, nws_count)); return (nws_array[cpunumber]); } /* * The default implementation of flow -> CPU ID mapping. * * Non-static so that protocols can use it to map their own work to specific * CPUs in a manner consistent to netisr for affinity purposes. */ u_int netisr_default_flow2cpu(u_int flowid) { return (nws_array[flowid % nws_count]); } /* * Dispatch tunable and sysctl configuration. */ struct netisr_dispatch_table_entry { u_int ndte_policy; const char *ndte_policy_str; }; static const struct netisr_dispatch_table_entry netisr_dispatch_table[] = { { NETISR_DISPATCH_DEFAULT, "default" }, { NETISR_DISPATCH_DEFERRED, "deferred" }, { NETISR_DISPATCH_HYBRID, "hybrid" }, { NETISR_DISPATCH_DIRECT, "direct" }, }; static const u_int netisr_dispatch_table_len = (sizeof(netisr_dispatch_table) / sizeof(netisr_dispatch_table[0])); static void netisr_dispatch_policy_to_str(u_int dispatch_policy, char *buffer, u_int buflen) { const struct netisr_dispatch_table_entry *ndtep; const char *str; u_int i; str = "unknown"; for (i = 0; i < netisr_dispatch_table_len; i++) { ndtep = &netisr_dispatch_table[i]; if (ndtep->ndte_policy == dispatch_policy) { str = ndtep->ndte_policy_str; break; } } snprintf(buffer, buflen, "%s", str); } static int netisr_dispatch_policy_from_str(const char *str, u_int *dispatch_policyp) { const struct netisr_dispatch_table_entry *ndtep; u_int i; for (i = 0; i < netisr_dispatch_table_len; i++) { ndtep = &netisr_dispatch_table[i]; if (strcmp(ndtep->ndte_policy_str, str) == 0) { *dispatch_policyp = ndtep->ndte_policy; return (0); } } return (EINVAL); } static int sysctl_netisr_dispatch_policy(SYSCTL_HANDLER_ARGS) { char tmp[NETISR_DISPATCH_POLICY_MAXSTR]; u_int dispatch_policy; int error; netisr_dispatch_policy_to_str(netisr_dispatch_policy, tmp, sizeof(tmp)); error = sysctl_handle_string(oidp, tmp, sizeof(tmp), req); if (error == 0 && req->newptr != NULL) { error = netisr_dispatch_policy_from_str(tmp, &dispatch_policy); if (error == 0 && dispatch_policy == NETISR_DISPATCH_DEFAULT) error = EINVAL; if (error == 0) netisr_dispatch_policy = dispatch_policy; } return (error); } /* * Register a new netisr handler, which requires initializing per-protocol * fields for each workstream. All netisr work is briefly suspended while * the protocol is installed. */ void netisr_register(const struct netisr_handler *nhp) { struct netisr_work *npwp; const char *name; u_int i, proto; proto = nhp->nh_proto; name = nhp->nh_name; /* * Test that the requested registration is valid. */ KASSERT(nhp->nh_name != NULL, ("%s: nh_name NULL for %u", __func__, proto)); KASSERT(nhp->nh_handler != NULL, ("%s: nh_handler NULL for %s", __func__, name)); KASSERT(nhp->nh_policy == NETISR_POLICY_SOURCE || nhp->nh_policy == NETISR_POLICY_FLOW || nhp->nh_policy == NETISR_POLICY_CPU, ("%s: unsupported nh_policy %u for %s", __func__, nhp->nh_policy, name)); KASSERT(nhp->nh_policy == NETISR_POLICY_FLOW || nhp->nh_m2flow == NULL, ("%s: nh_policy != FLOW but m2flow defined for %s", __func__, name)); KASSERT(nhp->nh_policy == NETISR_POLICY_CPU || nhp->nh_m2cpuid == NULL, ("%s: nh_policy != CPU but m2cpuid defined for %s", __func__, name)); KASSERT(nhp->nh_policy != NETISR_POLICY_CPU || nhp->nh_m2cpuid != NULL, ("%s: nh_policy == CPU but m2cpuid not defined for %s", __func__, name)); KASSERT(nhp->nh_dispatch == NETISR_DISPATCH_DEFAULT || nhp->nh_dispatch == NETISR_DISPATCH_DEFERRED || nhp->nh_dispatch == NETISR_DISPATCH_HYBRID || nhp->nh_dispatch == NETISR_DISPATCH_DIRECT, ("%s: invalid nh_dispatch (%u)", __func__, nhp->nh_dispatch)); KASSERT(proto < NETISR_MAXPROT, ("%s(%u, %s): protocol too big", __func__, proto, name)); /* * Test that no existing registration exists for this protocol. */ NETISR_WLOCK(); KASSERT(netisr_proto[proto].np_name == NULL, ("%s(%u, %s): name present", __func__, proto, name)); KASSERT(netisr_proto[proto].np_handler == NULL, ("%s(%u, %s): handler present", __func__, proto, name)); netisr_proto[proto].np_name = name; netisr_proto[proto].np_handler = nhp->nh_handler; netisr_proto[proto].np_m2flow = nhp->nh_m2flow; netisr_proto[proto].np_m2cpuid = nhp->nh_m2cpuid; netisr_proto[proto].np_drainedcpu = nhp->nh_drainedcpu; if (nhp->nh_qlimit == 0) netisr_proto[proto].np_qlimit = netisr_defaultqlimit; else if (nhp->nh_qlimit > netisr_maxqlimit) { printf("%s: %s requested queue limit %u capped to " "net.isr.maxqlimit %u\n", __func__, name, nhp->nh_qlimit, netisr_maxqlimit); netisr_proto[proto].np_qlimit = netisr_maxqlimit; } else netisr_proto[proto].np_qlimit = nhp->nh_qlimit; netisr_proto[proto].np_policy = nhp->nh_policy; netisr_proto[proto].np_dispatch = nhp->nh_dispatch; CPU_FOREACH(i) { npwp = &(DPCPU_ID_PTR(i, nws))->nws_work[proto]; bzero(npwp, sizeof(*npwp)); npwp->nw_qlimit = netisr_proto[proto].np_qlimit; } NETISR_WUNLOCK(); } /* * Clear drop counters across all workstreams for a protocol. */ void netisr_clearqdrops(const struct netisr_handler *nhp) { struct netisr_work *npwp; #ifdef INVARIANTS const char *name; #endif u_int i, proto; proto = nhp->nh_proto; #ifdef INVARIANTS name = nhp->nh_name; #endif KASSERT(proto < NETISR_MAXPROT, ("%s(%u): protocol too big for %s", __func__, proto, name)); NETISR_WLOCK(); KASSERT(netisr_proto[proto].np_handler != NULL, ("%s(%u): protocol not registered for %s", __func__, proto, name)); CPU_FOREACH(i) { npwp = &(DPCPU_ID_PTR(i, nws))->nws_work[proto]; npwp->nw_qdrops = 0; } NETISR_WUNLOCK(); } /* * Query current drop counters across all workstreams for a protocol. */ void netisr_getqdrops(const struct netisr_handler *nhp, u_int64_t *qdropp) { struct netisr_work *npwp; struct rm_priotracker tracker; #ifdef INVARIANTS const char *name; #endif u_int i, proto; *qdropp = 0; proto = nhp->nh_proto; #ifdef INVARIANTS name = nhp->nh_name; #endif KASSERT(proto < NETISR_MAXPROT, ("%s(%u): protocol too big for %s", __func__, proto, name)); NETISR_RLOCK(&tracker); KASSERT(netisr_proto[proto].np_handler != NULL, ("%s(%u): protocol not registered for %s", __func__, proto, name)); CPU_FOREACH(i) { npwp = &(DPCPU_ID_PTR(i, nws))->nws_work[proto]; *qdropp += npwp->nw_qdrops; } NETISR_RUNLOCK(&tracker); } /* * Query current per-workstream queue limit for a protocol. */ void netisr_getqlimit(const struct netisr_handler *nhp, u_int *qlimitp) { struct rm_priotracker tracker; #ifdef INVARIANTS const char *name; #endif u_int proto; proto = nhp->nh_proto; #ifdef INVARIANTS name = nhp->nh_name; #endif KASSERT(proto < NETISR_MAXPROT, ("%s(%u): protocol too big for %s", __func__, proto, name)); NETISR_RLOCK(&tracker); KASSERT(netisr_proto[proto].np_handler != NULL, ("%s(%u): protocol not registered for %s", __func__, proto, name)); *qlimitp = netisr_proto[proto].np_qlimit; NETISR_RUNLOCK(&tracker); } /* * Update the queue limit across per-workstream queues for a protocol. We * simply change the limits, and don't drain overflowed packets as they will * (hopefully) take care of themselves shortly. */ int netisr_setqlimit(const struct netisr_handler *nhp, u_int qlimit) { struct netisr_work *npwp; #ifdef INVARIANTS const char *name; #endif u_int i, proto; if (qlimit > netisr_maxqlimit) return (EINVAL); proto = nhp->nh_proto; #ifdef INVARIANTS name = nhp->nh_name; #endif KASSERT(proto < NETISR_MAXPROT, ("%s(%u): protocol too big for %s", __func__, proto, name)); NETISR_WLOCK(); KASSERT(netisr_proto[proto].np_handler != NULL, ("%s(%u): protocol not registered for %s", __func__, proto, name)); netisr_proto[proto].np_qlimit = qlimit; CPU_FOREACH(i) { npwp = &(DPCPU_ID_PTR(i, nws))->nws_work[proto]; npwp->nw_qlimit = qlimit; } NETISR_WUNLOCK(); return (0); } /* * Drain all packets currently held in a particular protocol work queue. */ static void netisr_drain_proto(struct netisr_work *npwp) { struct mbuf *m; /* * We would assert the lock on the workstream but it's not passed in. */ while ((m = npwp->nw_head) != NULL) { npwp->nw_head = m->m_nextpkt; m->m_nextpkt = NULL; if (npwp->nw_head == NULL) npwp->nw_tail = NULL; npwp->nw_len--; m_freem(m); } KASSERT(npwp->nw_tail == NULL, ("%s: tail", __func__)); KASSERT(npwp->nw_len == 0, ("%s: len", __func__)); } /* * Remove the registration of a network protocol, which requires clearing * per-protocol fields across all workstreams, including freeing all mbufs in * the queues at time of unregister. All work in netisr is briefly suspended * while this takes place. */ void netisr_unregister(const struct netisr_handler *nhp) { struct netisr_work *npwp; #ifdef INVARIANTS const char *name; #endif u_int i, proto; proto = nhp->nh_proto; #ifdef INVARIANTS name = nhp->nh_name; #endif KASSERT(proto < NETISR_MAXPROT, ("%s(%u): protocol too big for %s", __func__, proto, name)); NETISR_WLOCK(); KASSERT(netisr_proto[proto].np_handler != NULL, ("%s(%u): protocol not registered for %s", __func__, proto, name)); netisr_proto[proto].np_name = NULL; netisr_proto[proto].np_handler = NULL; netisr_proto[proto].np_m2flow = NULL; netisr_proto[proto].np_m2cpuid = NULL; netisr_proto[proto].np_qlimit = 0; netisr_proto[proto].np_policy = 0; CPU_FOREACH(i) { npwp = &(DPCPU_ID_PTR(i, nws))->nws_work[proto]; netisr_drain_proto(npwp); bzero(npwp, sizeof(*npwp)); } NETISR_WUNLOCK(); } /* * Compose the global and per-protocol policies on dispatch, and return the * dispatch policy to use. */ static u_int netisr_get_dispatch(struct netisr_proto *npp) { /* * Protocol-specific configuration overrides the global default. */ if (npp->np_dispatch != NETISR_DISPATCH_DEFAULT) return (npp->np_dispatch); return (netisr_dispatch_policy); } /* * Look up the workstream given a packet and source identifier. Do this by * checking the protocol's policy, and optionally call out to the protocol * for assistance if required. */ static struct mbuf * netisr_select_cpuid(struct netisr_proto *npp, u_int dispatch_policy, uintptr_t source, struct mbuf *m, u_int *cpuidp) { struct ifnet *ifp; u_int policy; NETISR_LOCK_ASSERT(); /* * In the event we have only one worker, shortcut and deliver to it * without further ado. */ if (nws_count == 1) { *cpuidp = nws_array[0]; return (m); } /* * What happens next depends on the policy selected by the protocol. * If we want to support per-interface policies, we should do that * here first. */ policy = npp->np_policy; if (policy == NETISR_POLICY_CPU) { m = npp->np_m2cpuid(m, source, cpuidp); if (m == NULL) return (NULL); /* * It's possible for a protocol not to have a good idea about * where to process a packet, in which case we fall back on * the netisr code to decide. In the hybrid case, return the * current CPU ID, which will force an immediate direct * dispatch. In the queued case, fall back on the SOURCE * policy. */ if (*cpuidp != NETISR_CPUID_NONE) return (m); if (dispatch_policy == NETISR_DISPATCH_HYBRID) { *cpuidp = curcpu; return (m); } policy = NETISR_POLICY_SOURCE; } if (policy == NETISR_POLICY_FLOW) { if (M_HASHTYPE_GET(m) == M_HASHTYPE_NONE && npp->np_m2flow != NULL) { m = npp->np_m2flow(m, source); if (m == NULL) return (NULL); } if (M_HASHTYPE_GET(m) != M_HASHTYPE_NONE) { *cpuidp = netisr_default_flow2cpu(m->m_pkthdr.flowid); return (m); } policy = NETISR_POLICY_SOURCE; } KASSERT(policy == NETISR_POLICY_SOURCE, ("%s: invalid policy %u for %s", __func__, npp->np_policy, npp->np_name)); ifp = m->m_pkthdr.rcvif; if (ifp != NULL) *cpuidp = nws_array[(ifp->if_index + source) % nws_count]; else *cpuidp = nws_array[source % nws_count]; return (m); } /* * Process packets associated with a workstream and protocol. For reasons of * fairness, we process up to one complete netisr queue at a time, moving the * queue to a stack-local queue for processing, but do not loop refreshing * from the global queue. The caller is responsible for deciding whether to * loop, and for setting the NWS_RUNNING flag. The passed workstream will be * locked on entry and relocked before return, but will be released while * processing. The number of packets processed is returned. */ static u_int netisr_process_workstream_proto(struct netisr_workstream *nwsp, u_int proto) { struct netisr_work local_npw, *npwp; u_int handled; struct mbuf *m; NETISR_LOCK_ASSERT(); NWS_LOCK_ASSERT(nwsp); KASSERT(nwsp->nws_flags & NWS_RUNNING, ("%s(%u): not running", __func__, proto)); KASSERT(proto >= 0 && proto < NETISR_MAXPROT, ("%s(%u): invalid proto\n", __func__, proto)); npwp = &nwsp->nws_work[proto]; if (npwp->nw_len == 0) return (0); /* * Move the global work queue to a thread-local work queue. * * Notice that this means the effective maximum length of the queue * is actually twice that of the maximum queue length specified in * the protocol registration call. */ handled = npwp->nw_len; local_npw = *npwp; npwp->nw_head = NULL; npwp->nw_tail = NULL; npwp->nw_len = 0; nwsp->nws_pendingbits &= ~(1 << proto); NWS_UNLOCK(nwsp); while ((m = local_npw.nw_head) != NULL) { local_npw.nw_head = m->m_nextpkt; m->m_nextpkt = NULL; if (local_npw.nw_head == NULL) local_npw.nw_tail = NULL; local_npw.nw_len--; VNET_ASSERT(m->m_pkthdr.rcvif != NULL, ("%s:%d rcvif == NULL: m=%p", __func__, __LINE__, m)); CURVNET_SET(m->m_pkthdr.rcvif->if_vnet); netisr_proto[proto].np_handler(m); CURVNET_RESTORE(); } KASSERT(local_npw.nw_len == 0, ("%s(%u): len %u", __func__, proto, local_npw.nw_len)); if (netisr_proto[proto].np_drainedcpu) netisr_proto[proto].np_drainedcpu(nwsp->nws_cpu); NWS_LOCK(nwsp); npwp->nw_handled += handled; return (handled); } /* * SWI handler for netisr -- processes packets in a set of workstreams that * it owns, woken up by calls to NWS_SIGNAL(). If this workstream is already * being direct dispatched, go back to sleep and wait for the dispatching * thread to wake us up again. */ static void swi_net(void *arg) { #ifdef NETISR_LOCKING struct rm_priotracker tracker; #endif struct netisr_workstream *nwsp; u_int bits, prot; nwsp = arg; #ifdef DEVICE_POLLING KASSERT(nws_count == 1, ("%s: device_polling but nws_count != 1", __func__)); netisr_poll(); #endif #ifdef NETISR_LOCKING NETISR_RLOCK(&tracker); #endif NWS_LOCK(nwsp); KASSERT(!(nwsp->nws_flags & NWS_RUNNING), ("swi_net: running")); if (nwsp->nws_flags & NWS_DISPATCHING) goto out; nwsp->nws_flags |= NWS_RUNNING; nwsp->nws_flags &= ~NWS_SCHEDULED; while ((bits = nwsp->nws_pendingbits) != 0) { while ((prot = ffs(bits)) != 0) { prot--; bits &= ~(1 << prot); (void)netisr_process_workstream_proto(nwsp, prot); } } nwsp->nws_flags &= ~NWS_RUNNING; out: NWS_UNLOCK(nwsp); #ifdef NETISR_LOCKING NETISR_RUNLOCK(&tracker); #endif #ifdef DEVICE_POLLING netisr_pollmore(); #endif } static int netisr_queue_workstream(struct netisr_workstream *nwsp, u_int proto, struct netisr_work *npwp, struct mbuf *m, int *dosignalp) { NWS_LOCK_ASSERT(nwsp); *dosignalp = 0; if (npwp->nw_len < npwp->nw_qlimit) { m->m_nextpkt = NULL; if (npwp->nw_head == NULL) { npwp->nw_head = m; npwp->nw_tail = m; } else { npwp->nw_tail->m_nextpkt = m; npwp->nw_tail = m; } npwp->nw_len++; if (npwp->nw_len > npwp->nw_watermark) npwp->nw_watermark = npwp->nw_len; /* * We must set the bit regardless of NWS_RUNNING, so that * swi_net() keeps calling netisr_process_workstream_proto(). */ nwsp->nws_pendingbits |= (1 << proto); if (!(nwsp->nws_flags & (NWS_RUNNING | NWS_DISPATCHING | NWS_SCHEDULED))) { nwsp->nws_flags |= NWS_SCHEDULED; *dosignalp = 1; /* Defer until unlocked. */ } npwp->nw_queued++; return (0); } else { m_freem(m); npwp->nw_qdrops++; return (ENOBUFS); } } static int netisr_queue_internal(u_int proto, struct mbuf *m, u_int cpuid) { struct netisr_workstream *nwsp; struct netisr_work *npwp; int dosignal, error; #ifdef NETISR_LOCKING NETISR_LOCK_ASSERT(); #endif KASSERT(cpuid <= mp_maxid, ("%s: cpuid too big (%u, %u)", __func__, cpuid, mp_maxid)); KASSERT(!CPU_ABSENT(cpuid), ("%s: CPU %u absent", __func__, cpuid)); dosignal = 0; error = 0; nwsp = DPCPU_ID_PTR(cpuid, nws); npwp = &nwsp->nws_work[proto]; NWS_LOCK(nwsp); error = netisr_queue_workstream(nwsp, proto, npwp, m, &dosignal); NWS_UNLOCK(nwsp); if (dosignal) NWS_SIGNAL(nwsp); return (error); } int netisr_queue_src(u_int proto, uintptr_t source, struct mbuf *m) { #ifdef NETISR_LOCKING struct rm_priotracker tracker; #endif u_int cpuid; int error; KASSERT(proto < NETISR_MAXPROT, ("%s: invalid proto %u", __func__, proto)); #ifdef NETISR_LOCKING NETISR_RLOCK(&tracker); #endif KASSERT(netisr_proto[proto].np_handler != NULL, ("%s: invalid proto %u", __func__, proto)); m = netisr_select_cpuid(&netisr_proto[proto], NETISR_DISPATCH_DEFERRED, source, m, &cpuid); if (m != NULL) { KASSERT(!CPU_ABSENT(cpuid), ("%s: CPU %u absent", __func__, cpuid)); error = netisr_queue_internal(proto, m, cpuid); } else error = ENOBUFS; #ifdef NETISR_LOCKING NETISR_RUNLOCK(&tracker); #endif return (error); } int netisr_queue(u_int proto, struct mbuf *m) { return (netisr_queue_src(proto, 0, m)); } /* * Dispatch a packet for netisr processing; direct dispatch is permitted by * calling context. */ int netisr_dispatch_src(u_int proto, uintptr_t source, struct mbuf *m) { #ifdef NETISR_LOCKING struct rm_priotracker tracker; #endif struct netisr_workstream *nwsp; struct netisr_proto *npp; struct netisr_work *npwp; int dosignal, error; u_int cpuid, dispatch_policy; KASSERT(proto < NETISR_MAXPROT, ("%s: invalid proto %u", __func__, proto)); #ifdef NETISR_LOCKING NETISR_RLOCK(&tracker); #endif npp = &netisr_proto[proto]; KASSERT(npp->np_handler != NULL, ("%s: invalid proto %u", __func__, proto)); dispatch_policy = netisr_get_dispatch(npp); if (dispatch_policy == NETISR_DISPATCH_DEFERRED) return (netisr_queue_src(proto, source, m)); /* * If direct dispatch is forced, then unconditionally dispatch * without a formal CPU selection. Borrow the current CPU's stats, * even if there's no worker on it. In this case we don't update * nws_flags because all netisr processing will be source ordered due * to always being forced to directly dispatch. */ if (dispatch_policy == NETISR_DISPATCH_DIRECT) { nwsp = DPCPU_PTR(nws); npwp = &nwsp->nws_work[proto]; npwp->nw_dispatched++; npwp->nw_handled++; netisr_proto[proto].np_handler(m); error = 0; goto out_unlock; } KASSERT(dispatch_policy == NETISR_DISPATCH_HYBRID, ("%s: unknown dispatch policy (%u)", __func__, dispatch_policy)); /* * Otherwise, we execute in a hybrid mode where we will try to direct * dispatch if we're on the right CPU and the netisr worker isn't * already running. */ sched_pin(); m = netisr_select_cpuid(&netisr_proto[proto], NETISR_DISPATCH_HYBRID, source, m, &cpuid); if (m == NULL) { error = ENOBUFS; goto out_unpin; } KASSERT(!CPU_ABSENT(cpuid), ("%s: CPU %u absent", __func__, cpuid)); if (cpuid != curcpu) goto queue_fallback; nwsp = DPCPU_PTR(nws); npwp = &nwsp->nws_work[proto]; /*- * We are willing to direct dispatch only if three conditions hold: * * (1) The netisr worker isn't already running, * (2) Another thread isn't already directly dispatching, and * (3) The netisr hasn't already been woken up. */ NWS_LOCK(nwsp); if (nwsp->nws_flags & (NWS_RUNNING | NWS_DISPATCHING | NWS_SCHEDULED)) { error = netisr_queue_workstream(nwsp, proto, npwp, m, &dosignal); NWS_UNLOCK(nwsp); if (dosignal) NWS_SIGNAL(nwsp); goto out_unpin; } /* * The current thread is now effectively the netisr worker, so set * the dispatching flag to prevent concurrent processing of the * stream from another thread (even the netisr worker), which could * otherwise lead to effective misordering of the stream. */ nwsp->nws_flags |= NWS_DISPATCHING; NWS_UNLOCK(nwsp); netisr_proto[proto].np_handler(m); NWS_LOCK(nwsp); nwsp->nws_flags &= ~NWS_DISPATCHING; npwp->nw_handled++; npwp->nw_hybrid_dispatched++; /* * If other work was enqueued by another thread while we were direct * dispatching, we need to signal the netisr worker to do that work. * In the future, we might want to do some of that work in the * current thread, rather than trigger further context switches. If * so, we'll want to establish a reasonable bound on the work done in * the "borrowed" context. */ if (nwsp->nws_pendingbits != 0) { nwsp->nws_flags |= NWS_SCHEDULED; dosignal = 1; } else dosignal = 0; NWS_UNLOCK(nwsp); if (dosignal) NWS_SIGNAL(nwsp); error = 0; goto out_unpin; queue_fallback: error = netisr_queue_internal(proto, m, cpuid); out_unpin: sched_unpin(); out_unlock: #ifdef NETISR_LOCKING NETISR_RUNLOCK(&tracker); #endif return (error); } int netisr_dispatch(u_int proto, struct mbuf *m) { return (netisr_dispatch_src(proto, 0, m)); } #ifdef DEVICE_POLLING /* * Kernel polling borrows a netisr thread to run interface polling in; this * function allows kernel polling to request that the netisr thread be * scheduled even if no packets are pending for protocols. */ void netisr_sched_poll(void) { struct netisr_workstream *nwsp; nwsp = DPCPU_ID_PTR(nws_array[0], nws); NWS_SIGNAL(nwsp); } #endif static void netisr_start_swi(u_int cpuid, struct pcpu *pc) { char swiname[12]; struct netisr_workstream *nwsp; int error; KASSERT(!CPU_ABSENT(cpuid), ("%s: CPU %u absent", __func__, cpuid)); nwsp = DPCPU_ID_PTR(cpuid, nws); mtx_init(&nwsp->nws_mtx, "netisr_mtx", NULL, MTX_DEF); nwsp->nws_cpu = cpuid; snprintf(swiname, sizeof(swiname), "netisr %u", cpuid); error = swi_add(&nwsp->nws_intr_event, swiname, swi_net, nwsp, SWI_NET, INTR_MPSAFE, &nwsp->nws_swi_cookie); if (error) panic("%s: swi_add %d", __func__, error); pc->pc_netisr = nwsp->nws_intr_event; if (netisr_bindthreads) { error = intr_event_bind(nwsp->nws_intr_event, cpuid); if (error != 0) printf("%s: cpu %u: intr_event_bind: %d", __func__, cpuid, error); } NETISR_WLOCK(); nws_array[nws_count] = nwsp->nws_cpu; nws_count++; NETISR_WUNLOCK(); } /* * Initialize the netisr subsystem. We rely on BSS and static initialization * of most fields in global data structures. * * Start a worker thread for the boot CPU so that we can support network * traffic immediately in case the network stack is used before additional * CPUs are started (for example, diskless boot). */ static void netisr_init(void *arg) { KASSERT(curcpu == 0, ("%s: not on CPU 0", __func__)); NETISR_LOCK_INIT(); if (netisr_maxthreads == 0 || netisr_maxthreads < -1 ) netisr_maxthreads = 1; /* default behavior */ else if (netisr_maxthreads == -1) netisr_maxthreads = mp_ncpus; /* use max cpus */ if (netisr_maxthreads > mp_ncpus) { printf("netisr_init: forcing maxthreads from %d to %d\n", netisr_maxthreads, mp_ncpus); netisr_maxthreads = mp_ncpus; } if (netisr_defaultqlimit > netisr_maxqlimit) { printf("netisr_init: forcing defaultqlimit from %d to %d\n", netisr_defaultqlimit, netisr_maxqlimit); netisr_defaultqlimit = netisr_maxqlimit; } #ifdef DEVICE_POLLING /* * The device polling code is not yet aware of how to deal with * multiple netisr threads, so for the time being compiling in device * polling disables parallel netisr workers. */ if (netisr_maxthreads != 1 || netisr_bindthreads != 0) { printf("netisr_init: forcing maxthreads to 1 and " "bindthreads to 0 for device polling\n"); netisr_maxthreads = 1; netisr_bindthreads = 0; } #endif netisr_start_swi(curcpu, pcpu_find(curcpu)); } SYSINIT(netisr_init, SI_SUB_SOFTINTR, SI_ORDER_FIRST, netisr_init, NULL); /* * Start worker threads for additional CPUs. No attempt to gracefully handle * work reassignment, we don't yet support dynamic reconfiguration. */ static void netisr_start(void *arg) { struct pcpu *pc; STAILQ_FOREACH(pc, &cpuhead, pc_allcpu) { if (nws_count >= netisr_maxthreads) break; - /* XXXRW: Is skipping absent CPUs still required here? */ - if (CPU_ABSENT(pc->pc_cpuid)) - continue; /* Worker will already be present for boot CPU. */ if (pc->pc_netisr != NULL) continue; netisr_start_swi(pc->pc_cpuid, pc); } } SYSINIT(netisr_start, SI_SUB_SMP, SI_ORDER_MIDDLE, netisr_start, NULL); /* * Sysctl monitoring for netisr: query a list of registered protocols. */ static int sysctl_netisr_proto(SYSCTL_HANDLER_ARGS) { struct rm_priotracker tracker; struct sysctl_netisr_proto *snpp, *snp_array; struct netisr_proto *npp; u_int counter, proto; int error; if (req->newptr != NULL) return (EINVAL); snp_array = malloc(sizeof(*snp_array) * NETISR_MAXPROT, M_TEMP, M_ZERO | M_WAITOK); counter = 0; NETISR_RLOCK(&tracker); for (proto = 0; proto < NETISR_MAXPROT; proto++) { npp = &netisr_proto[proto]; if (npp->np_name == NULL) continue; snpp = &snp_array[counter]; snpp->snp_version = sizeof(*snpp); strlcpy(snpp->snp_name, npp->np_name, NETISR_NAMEMAXLEN); snpp->snp_proto = proto; snpp->snp_qlimit = npp->np_qlimit; snpp->snp_policy = npp->np_policy; snpp->snp_dispatch = npp->np_dispatch; if (npp->np_m2flow != NULL) snpp->snp_flags |= NETISR_SNP_FLAGS_M2FLOW; if (npp->np_m2cpuid != NULL) snpp->snp_flags |= NETISR_SNP_FLAGS_M2CPUID; if (npp->np_drainedcpu != NULL) snpp->snp_flags |= NETISR_SNP_FLAGS_DRAINEDCPU; counter++; } NETISR_RUNLOCK(&tracker); KASSERT(counter <= NETISR_MAXPROT, ("sysctl_netisr_proto: counter too big (%d)", counter)); error = SYSCTL_OUT(req, snp_array, sizeof(*snp_array) * counter); free(snp_array, M_TEMP); return (error); } SYSCTL_PROC(_net_isr, OID_AUTO, proto, CTLFLAG_RD|CTLTYPE_STRUCT|CTLFLAG_MPSAFE, 0, 0, sysctl_netisr_proto, "S,sysctl_netisr_proto", "Return list of protocols registered with netisr"); /* * Sysctl monitoring for netisr: query a list of workstreams. */ static int sysctl_netisr_workstream(SYSCTL_HANDLER_ARGS) { struct rm_priotracker tracker; struct sysctl_netisr_workstream *snwsp, *snws_array; struct netisr_workstream *nwsp; u_int counter, cpuid; int error; if (req->newptr != NULL) return (EINVAL); snws_array = malloc(sizeof(*snws_array) * MAXCPU, M_TEMP, M_ZERO | M_WAITOK); counter = 0; NETISR_RLOCK(&tracker); CPU_FOREACH(cpuid) { nwsp = DPCPU_ID_PTR(cpuid, nws); if (nwsp->nws_intr_event == NULL) continue; NWS_LOCK(nwsp); snwsp = &snws_array[counter]; snwsp->snws_version = sizeof(*snwsp); /* * For now, we equate workstream IDs and CPU IDs in the * kernel, but expose them independently to userspace in case * that assumption changes in the future. */ snwsp->snws_wsid = cpuid; snwsp->snws_cpu = cpuid; if (nwsp->nws_intr_event != NULL) snwsp->snws_flags |= NETISR_SNWS_FLAGS_INTR; NWS_UNLOCK(nwsp); counter++; } NETISR_RUNLOCK(&tracker); KASSERT(counter <= MAXCPU, ("sysctl_netisr_workstream: counter too big (%d)", counter)); error = SYSCTL_OUT(req, snws_array, sizeof(*snws_array) * counter); free(snws_array, M_TEMP); return (error); } SYSCTL_PROC(_net_isr, OID_AUTO, workstream, CTLFLAG_RD|CTLTYPE_STRUCT|CTLFLAG_MPSAFE, 0, 0, sysctl_netisr_workstream, "S,sysctl_netisr_workstream", "Return list of workstreams implemented by netisr"); /* * Sysctl monitoring for netisr: query per-protocol data across all * workstreams. */ static int sysctl_netisr_work(SYSCTL_HANDLER_ARGS) { struct rm_priotracker tracker; struct sysctl_netisr_work *snwp, *snw_array; struct netisr_workstream *nwsp; struct netisr_proto *npp; struct netisr_work *nwp; u_int counter, cpuid, proto; int error; if (req->newptr != NULL) return (EINVAL); snw_array = malloc(sizeof(*snw_array) * MAXCPU * NETISR_MAXPROT, M_TEMP, M_ZERO | M_WAITOK); counter = 0; NETISR_RLOCK(&tracker); CPU_FOREACH(cpuid) { nwsp = DPCPU_ID_PTR(cpuid, nws); if (nwsp->nws_intr_event == NULL) continue; NWS_LOCK(nwsp); for (proto = 0; proto < NETISR_MAXPROT; proto++) { npp = &netisr_proto[proto]; if (npp->np_name == NULL) continue; nwp = &nwsp->nws_work[proto]; snwp = &snw_array[counter]; snwp->snw_version = sizeof(*snwp); snwp->snw_wsid = cpuid; /* See comment above. */ snwp->snw_proto = proto; snwp->snw_len = nwp->nw_len; snwp->snw_watermark = nwp->nw_watermark; snwp->snw_dispatched = nwp->nw_dispatched; snwp->snw_hybrid_dispatched = nwp->nw_hybrid_dispatched; snwp->snw_qdrops = nwp->nw_qdrops; snwp->snw_queued = nwp->nw_queued; snwp->snw_handled = nwp->nw_handled; counter++; } NWS_UNLOCK(nwsp); } KASSERT(counter <= MAXCPU * NETISR_MAXPROT, ("sysctl_netisr_work: counter too big (%d)", counter)); NETISR_RUNLOCK(&tracker); error = SYSCTL_OUT(req, snw_array, sizeof(*snw_array) * counter); free(snw_array, M_TEMP); return (error); } SYSCTL_PROC(_net_isr, OID_AUTO, work, CTLFLAG_RD|CTLTYPE_STRUCT|CTLFLAG_MPSAFE, 0, 0, sysctl_netisr_work, "S,sysctl_netisr_work", "Return list of per-workstream, per-protocol work in netisr"); #ifdef DDB DB_SHOW_COMMAND(netisr, db_show_netisr) { struct netisr_workstream *nwsp; struct netisr_work *nwp; int first, proto; u_int cpuid; db_printf("%3s %6s %5s %5s %5s %8s %8s %8s %8s\n", "CPU", "Proto", "Len", "WMark", "Max", "Disp", "HDisp", "Drop", "Queue"); CPU_FOREACH(cpuid) { nwsp = DPCPU_ID_PTR(cpuid, nws); if (nwsp->nws_intr_event == NULL) continue; first = 1; for (proto = 0; proto < NETISR_MAXPROT; proto++) { if (netisr_proto[proto].np_handler == NULL) continue; nwp = &nwsp->nws_work[proto]; if (first) { db_printf("%3d ", cpuid); first = 0; } else db_printf("%3s ", ""); db_printf( "%6s %5d %5d %5d %8ju %8ju %8ju %8ju\n", netisr_proto[proto].np_name, nwp->nw_len, nwp->nw_watermark, nwp->nw_qlimit, nwp->nw_dispatched, nwp->nw_hybrid_dispatched, nwp->nw_qdrops, nwp->nw_queued); } } } #endif Index: projects/release-pkg/sys/net80211/ieee80211.c =================================================================== --- projects/release-pkg/sys/net80211/ieee80211.c (revision 297604) +++ projects/release-pkg/sys/net80211/ieee80211.c (revision 297605) @@ -1,1819 +1,1819 @@ /*- * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting * 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$"); /* * IEEE 802.11 generic handler */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #include #include const char *ieee80211_phymode_name[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = "auto", [IEEE80211_MODE_11A] = "11a", [IEEE80211_MODE_11B] = "11b", [IEEE80211_MODE_11G] = "11g", [IEEE80211_MODE_FH] = "FH", [IEEE80211_MODE_TURBO_A] = "turboA", [IEEE80211_MODE_TURBO_G] = "turboG", [IEEE80211_MODE_STURBO_A] = "sturboA", [IEEE80211_MODE_HALF] = "half", [IEEE80211_MODE_QUARTER] = "quarter", [IEEE80211_MODE_11NA] = "11na", [IEEE80211_MODE_11NG] = "11ng", }; /* map ieee80211_opmode to the corresponding capability bit */ const int ieee80211_opcap[IEEE80211_OPMODE_MAX] = { [IEEE80211_M_IBSS] = IEEE80211_C_IBSS, [IEEE80211_M_WDS] = IEEE80211_C_WDS, [IEEE80211_M_STA] = IEEE80211_C_STA, [IEEE80211_M_AHDEMO] = IEEE80211_C_AHDEMO, [IEEE80211_M_HOSTAP] = IEEE80211_C_HOSTAP, [IEEE80211_M_MONITOR] = IEEE80211_C_MONITOR, #ifdef IEEE80211_SUPPORT_MESH [IEEE80211_M_MBSS] = IEEE80211_C_MBSS, #endif }; const uint8_t ieee80211broadcastaddr[IEEE80211_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; static void ieee80211_syncflag_locked(struct ieee80211com *ic, int flag); static void ieee80211_syncflag_ht_locked(struct ieee80211com *ic, int flag); static void ieee80211_syncflag_ext_locked(struct ieee80211com *ic, int flag); static int ieee80211_media_setup(struct ieee80211com *ic, struct ifmedia *media, int caps, int addsta, ifm_change_cb_t media_change, ifm_stat_cb_t media_stat); static int media_status(enum ieee80211_opmode, const struct ieee80211_channel *); static uint64_t ieee80211_get_counter(struct ifnet *, ift_counter); MALLOC_DEFINE(M_80211_VAP, "80211vap", "802.11 vap state"); /* * Default supported rates for 802.11 operation (in IEEE .5Mb units). */ #define B(r) ((r) | IEEE80211_RATE_BASIC) static const struct ieee80211_rateset ieee80211_rateset_11a = { 8, { B(12), 18, B(24), 36, B(48), 72, 96, 108 } }; static const struct ieee80211_rateset ieee80211_rateset_half = { 8, { B(6), 9, B(12), 18, B(24), 36, 48, 54 } }; static const struct ieee80211_rateset ieee80211_rateset_quarter = { 8, { B(3), 4, B(6), 9, B(12), 18, 24, 27 } }; static const struct ieee80211_rateset ieee80211_rateset_11b = { 4, { B(2), B(4), B(11), B(22) } }; /* NB: OFDM rates are handled specially based on mode */ static const struct ieee80211_rateset ieee80211_rateset_11g = { 12, { B(2), B(4), B(11), B(22), 12, 18, 24, 36, 48, 72, 96, 108 } }; #undef B /* * Fill in 802.11 available channel set, mark * all available channels as active, and pick * a default channel if not already specified. */ void ieee80211_chan_init(struct ieee80211com *ic) { #define DEFAULTRATES(m, def) do { \ if (ic->ic_sup_rates[m].rs_nrates == 0) \ ic->ic_sup_rates[m] = def; \ } while (0) struct ieee80211_channel *c; int i; KASSERT(0 < ic->ic_nchans && ic->ic_nchans <= IEEE80211_CHAN_MAX, ("invalid number of channels specified: %u", ic->ic_nchans)); memset(ic->ic_chan_avail, 0, sizeof(ic->ic_chan_avail)); memset(ic->ic_modecaps, 0, sizeof(ic->ic_modecaps)); setbit(ic->ic_modecaps, IEEE80211_MODE_AUTO); for (i = 0; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; KASSERT(c->ic_flags != 0, ("channel with no flags")); /* * Help drivers that work only with frequencies by filling * in IEEE channel #'s if not already calculated. Note this * mimics similar work done in ieee80211_setregdomain when * changing regulatory state. */ if (c->ic_ieee == 0) c->ic_ieee = ieee80211_mhz2ieee(c->ic_freq,c->ic_flags); if (IEEE80211_IS_CHAN_HT40(c) && c->ic_extieee == 0) c->ic_extieee = ieee80211_mhz2ieee(c->ic_freq + (IEEE80211_IS_CHAN_HT40U(c) ? 20 : -20), c->ic_flags); /* default max tx power to max regulatory */ if (c->ic_maxpower == 0) c->ic_maxpower = 2*c->ic_maxregpower; setbit(ic->ic_chan_avail, c->ic_ieee); /* * Identify mode capabilities. */ if (IEEE80211_IS_CHAN_A(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11A); if (IEEE80211_IS_CHAN_B(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11B); if (IEEE80211_IS_CHAN_ANYG(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11G); if (IEEE80211_IS_CHAN_FHSS(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_FH); if (IEEE80211_IS_CHAN_108A(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_TURBO_A); if (IEEE80211_IS_CHAN_108G(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_TURBO_G); if (IEEE80211_IS_CHAN_ST(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_STURBO_A); if (IEEE80211_IS_CHAN_HALF(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_HALF); if (IEEE80211_IS_CHAN_QUARTER(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_QUARTER); if (IEEE80211_IS_CHAN_HTA(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11NA); if (IEEE80211_IS_CHAN_HTG(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11NG); } /* initialize candidate channels to all available */ memcpy(ic->ic_chan_active, ic->ic_chan_avail, sizeof(ic->ic_chan_avail)); /* sort channel table to allow lookup optimizations */ ieee80211_sort_channels(ic->ic_channels, ic->ic_nchans); /* invalidate any previous state */ ic->ic_bsschan = IEEE80211_CHAN_ANYC; ic->ic_prevchan = NULL; ic->ic_csa_newchan = NULL; /* arbitrarily pick the first channel */ ic->ic_curchan = &ic->ic_channels[0]; ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); /* fillin well-known rate sets if driver has not specified */ DEFAULTRATES(IEEE80211_MODE_11B, ieee80211_rateset_11b); DEFAULTRATES(IEEE80211_MODE_11G, ieee80211_rateset_11g); DEFAULTRATES(IEEE80211_MODE_11A, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_TURBO_A, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_TURBO_G, ieee80211_rateset_11g); DEFAULTRATES(IEEE80211_MODE_STURBO_A, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_HALF, ieee80211_rateset_half); DEFAULTRATES(IEEE80211_MODE_QUARTER, ieee80211_rateset_quarter); DEFAULTRATES(IEEE80211_MODE_11NA, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_11NG, ieee80211_rateset_11g); /* * Setup required information to fill the mcsset field, if driver did * not. Assume a 2T2R setup for historic reasons. */ if (ic->ic_rxstream == 0) ic->ic_rxstream = 2; if (ic->ic_txstream == 0) ic->ic_txstream = 2; /* * Set auto mode to reset active channel state and any desired channel. */ (void) ieee80211_setmode(ic, IEEE80211_MODE_AUTO); #undef DEFAULTRATES } static void null_update_mcast(struct ieee80211com *ic) { ic_printf(ic, "need multicast update callback\n"); } static void null_update_promisc(struct ieee80211com *ic) { ic_printf(ic, "need promiscuous mode update callback\n"); } static void null_update_chw(struct ieee80211com *ic) { ic_printf(ic, "%s: need callback\n", __func__); } int ic_printf(struct ieee80211com *ic, const char * fmt, ...) { va_list ap; int retval; retval = printf("%s: ", ic->ic_name); va_start(ap, fmt); retval += vprintf(fmt, ap); va_end(ap); return (retval); } static LIST_HEAD(, ieee80211com) ic_head = LIST_HEAD_INITIALIZER(ic_head); static struct mtx ic_list_mtx; MTX_SYSINIT(ic_list, &ic_list_mtx, "ieee80211com list", MTX_DEF); static int sysctl_ieee80211coms(SYSCTL_HANDLER_ARGS) { struct ieee80211com *ic; struct sbuf sb; char *sp; int error; error = sysctl_wire_old_buffer(req, 0); if (error) return (error); sbuf_new_for_sysctl(&sb, NULL, 8, req); sbuf_clear_flags(&sb, SBUF_INCLUDENUL); sp = ""; mtx_lock(&ic_list_mtx); LIST_FOREACH(ic, &ic_head, ic_next) { sbuf_printf(&sb, "%s%s", sp, ic->ic_name); sp = " "; } mtx_unlock(&ic_list_mtx); error = sbuf_finish(&sb); sbuf_delete(&sb); return (error); } SYSCTL_PROC(_net_wlan, OID_AUTO, devices, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, 0, sysctl_ieee80211coms, "A", "names of available 802.11 devices"); /* * Attach/setup the common net80211 state. Called by * the driver on attach to prior to creating any vap's. */ void ieee80211_ifattach(struct ieee80211com *ic) { IEEE80211_LOCK_INIT(ic, ic->ic_name); IEEE80211_TX_LOCK_INIT(ic, ic->ic_name); TAILQ_INIT(&ic->ic_vaps); /* Create a taskqueue for all state changes */ ic->ic_tq = taskqueue_create("ic_taskq", M_WAITOK | M_ZERO, taskqueue_thread_enqueue, &ic->ic_tq); taskqueue_start_threads(&ic->ic_tq, 1, PI_NET, "%s net80211 taskq", ic->ic_name); ic->ic_ierrors = counter_u64_alloc(M_WAITOK); ic->ic_oerrors = counter_u64_alloc(M_WAITOK); /* * Fill in 802.11 available channel set, mark all * available channels as active, and pick a default * channel if not already specified. */ ieee80211_chan_init(ic); ic->ic_update_mcast = null_update_mcast; ic->ic_update_promisc = null_update_promisc; ic->ic_update_chw = null_update_chw; ic->ic_hash_key = arc4random(); ic->ic_bintval = IEEE80211_BINTVAL_DEFAULT; ic->ic_lintval = ic->ic_bintval; ic->ic_txpowlimit = IEEE80211_TXPOWER_MAX; ieee80211_crypto_attach(ic); ieee80211_node_attach(ic); ieee80211_power_attach(ic); ieee80211_proto_attach(ic); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_attach(ic); #endif ieee80211_ht_attach(ic); ieee80211_scan_attach(ic); ieee80211_regdomain_attach(ic); ieee80211_dfs_attach(ic); ieee80211_sysctl_attach(ic); mtx_lock(&ic_list_mtx); LIST_INSERT_HEAD(&ic_head, ic, ic_next); mtx_unlock(&ic_list_mtx); } /* * Detach net80211 state on device detach. Tear down * all vap's and reclaim all common state prior to the * device state going away. Note we may call back into * driver; it must be prepared for this. */ void ieee80211_ifdetach(struct ieee80211com *ic) { struct ieee80211vap *vap; mtx_lock(&ic_list_mtx); LIST_REMOVE(ic, ic_next); mtx_unlock(&ic_list_mtx); taskqueue_drain(taskqueue_thread, &ic->ic_restart_task); /* * The VAP is responsible for setting and clearing * the VIMAGE context. */ while ((vap = TAILQ_FIRST(&ic->ic_vaps)) != NULL) ieee80211_vap_destroy(vap); ieee80211_waitfor_parent(ic); ieee80211_sysctl_detach(ic); ieee80211_dfs_detach(ic); ieee80211_regdomain_detach(ic); ieee80211_scan_detach(ic); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_detach(ic); #endif ieee80211_ht_detach(ic); /* NB: must be called before ieee80211_node_detach */ ieee80211_proto_detach(ic); ieee80211_crypto_detach(ic); ieee80211_power_detach(ic); ieee80211_node_detach(ic); counter_u64_free(ic->ic_ierrors); counter_u64_free(ic->ic_oerrors); taskqueue_free(ic->ic_tq); IEEE80211_TX_LOCK_DESTROY(ic); IEEE80211_LOCK_DESTROY(ic); } struct ieee80211com * ieee80211_find_com(const char *name) { struct ieee80211com *ic; mtx_lock(&ic_list_mtx); LIST_FOREACH(ic, &ic_head, ic_next) if (strcmp(ic->ic_name, name) == 0) break; mtx_unlock(&ic_list_mtx); return (ic); } /* * Default reset method for use with the ioctl support. This * method is invoked after any state change in the 802.11 * layer that should be propagated to the hardware but not * require re-initialization of the 802.11 state machine (e.g * rescanning for an ap). We always return ENETRESET which * should cause the driver to re-initialize the device. Drivers * can override this method to implement more optimized support. */ static int default_reset(struct ieee80211vap *vap, u_long cmd) { return ENETRESET; } /* * Add underlying device errors to vap errors. */ static uint64_t ieee80211_get_counter(struct ifnet *ifp, ift_counter cnt) { struct ieee80211vap *vap = ifp->if_softc; struct ieee80211com *ic = vap->iv_ic; uint64_t rv; rv = if_get_counter_default(ifp, cnt); switch (cnt) { case IFCOUNTER_OERRORS: rv += counter_u64_fetch(ic->ic_oerrors); break; case IFCOUNTER_IERRORS: rv += counter_u64_fetch(ic->ic_ierrors); break; default: break; } return (rv); } /* * Prepare a vap for use. Drivers use this call to * setup net80211 state in new vap's prior attaching * them with ieee80211_vap_attach (below). */ int ieee80211_vap_setup(struct ieee80211com *ic, struct ieee80211vap *vap, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN]) { struct ifnet *ifp; ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { ic_printf(ic, "%s: unable to allocate ifnet\n", __func__); return ENOMEM; } if_initname(ifp, name, unit); ifp->if_softc = vap; /* back pointer */ ifp->if_flags = IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST; ifp->if_transmit = ieee80211_vap_transmit; ifp->if_qflush = ieee80211_vap_qflush; ifp->if_ioctl = ieee80211_ioctl; ifp->if_init = ieee80211_init; ifp->if_get_counter = ieee80211_get_counter; vap->iv_ifp = ifp; vap->iv_ic = ic; vap->iv_flags = ic->ic_flags; /* propagate common flags */ vap->iv_flags_ext = ic->ic_flags_ext; vap->iv_flags_ven = ic->ic_flags_ven; vap->iv_caps = ic->ic_caps &~ IEEE80211_C_OPMODE; vap->iv_htcaps = ic->ic_htcaps; vap->iv_htextcaps = ic->ic_htextcaps; vap->iv_opmode = opmode; vap->iv_caps |= ieee80211_opcap[opmode]; - vap->iv_myaddr = ic->ic_macaddr; + IEEE80211_ADDR_COPY(vap->iv_myaddr, ic->ic_macaddr); switch (opmode) { case IEEE80211_M_WDS: /* * WDS links must specify the bssid of the far end. * For legacy operation this is a static relationship. * For non-legacy operation the station must associate * and be authorized to pass traffic. Plumbing the * vap to the proper node happens when the vap * transitions to RUN state. */ IEEE80211_ADDR_COPY(vap->iv_des_bssid, bssid); vap->iv_flags |= IEEE80211_F_DESBSSID; if (flags & IEEE80211_CLONE_WDSLEGACY) vap->iv_flags_ext |= IEEE80211_FEXT_WDSLEGACY; break; #ifdef IEEE80211_SUPPORT_TDMA case IEEE80211_M_AHDEMO: if (flags & IEEE80211_CLONE_TDMA) { /* NB: checked before clone operation allowed */ KASSERT(ic->ic_caps & IEEE80211_C_TDMA, ("not TDMA capable, ic_caps 0x%x", ic->ic_caps)); /* * Propagate TDMA capability to mark vap; this * cannot be removed and is used to distinguish * regular ahdemo operation from ahdemo+tdma. */ vap->iv_caps |= IEEE80211_C_TDMA; } break; #endif default: break; } /* auto-enable s/w beacon miss support */ if (flags & IEEE80211_CLONE_NOBEACONS) vap->iv_flags_ext |= IEEE80211_FEXT_SWBMISS; /* auto-generated or user supplied MAC address */ if (flags & (IEEE80211_CLONE_BSSID|IEEE80211_CLONE_MACADDR)) vap->iv_flags_ext |= IEEE80211_FEXT_UNIQMAC; /* * Enable various functionality by default if we're * capable; the driver can override us if it knows better. */ if (vap->iv_caps & IEEE80211_C_WME) vap->iv_flags |= IEEE80211_F_WME; if (vap->iv_caps & IEEE80211_C_BURST) vap->iv_flags |= IEEE80211_F_BURST; /* NB: bg scanning only makes sense for station mode right now */ if (vap->iv_opmode == IEEE80211_M_STA && (vap->iv_caps & IEEE80211_C_BGSCAN)) vap->iv_flags |= IEEE80211_F_BGSCAN; vap->iv_flags |= IEEE80211_F_DOTH; /* XXX no cap, just ena */ /* NB: DFS support only makes sense for ap mode right now */ if (vap->iv_opmode == IEEE80211_M_HOSTAP && (vap->iv_caps & IEEE80211_C_DFS)) vap->iv_flags_ext |= IEEE80211_FEXT_DFS; vap->iv_des_chan = IEEE80211_CHAN_ANYC; /* any channel is ok */ vap->iv_bmissthreshold = IEEE80211_HWBMISS_DEFAULT; vap->iv_dtim_period = IEEE80211_DTIM_DEFAULT; /* * Install a default reset method for the ioctl support; * the driver can override this. */ vap->iv_reset = default_reset; ieee80211_sysctl_vattach(vap); ieee80211_crypto_vattach(vap); ieee80211_node_vattach(vap); ieee80211_power_vattach(vap); ieee80211_proto_vattach(vap); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_vattach(vap); #endif ieee80211_ht_vattach(vap); ieee80211_scan_vattach(vap); ieee80211_regdomain_vattach(vap); ieee80211_radiotap_vattach(vap); ieee80211_ratectl_set(vap, IEEE80211_RATECTL_NONE); return 0; } /* * Activate a vap. State should have been prepared with a * call to ieee80211_vap_setup and by the driver. On return * from this call the vap is ready for use. */ int ieee80211_vap_attach(struct ieee80211vap *vap, ifm_change_cb_t media_change, ifm_stat_cb_t media_stat, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ifnet *ifp = vap->iv_ifp; struct ieee80211com *ic = vap->iv_ic; struct ifmediareq imr; int maxrate; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s parent %s flags 0x%x flags_ext 0x%x\n", __func__, ieee80211_opmode_name[vap->iv_opmode], ic->ic_name, vap->iv_flags, vap->iv_flags_ext); /* * Do late attach work that cannot happen until after * the driver has had a chance to override defaults. */ ieee80211_node_latevattach(vap); ieee80211_power_latevattach(vap); maxrate = ieee80211_media_setup(ic, &vap->iv_media, vap->iv_caps, vap->iv_opmode == IEEE80211_M_STA, media_change, media_stat); ieee80211_media_status(ifp, &imr); /* NB: strip explicit mode; we're actually in autoselect */ ifmedia_set(&vap->iv_media, imr.ifm_active &~ (IFM_MMASK | IFM_IEEE80211_TURBO)); if (maxrate) ifp->if_baudrate = IF_Mbps(maxrate); ether_ifattach(ifp, macaddr); - vap->iv_myaddr = IF_LLADDR(ifp); + IEEE80211_ADDR_COPY(vap->iv_myaddr, IF_LLADDR(ifp)); /* hook output method setup by ether_ifattach */ vap->iv_output = ifp->if_output; ifp->if_output = ieee80211_output; /* NB: if_mtu set by ether_ifattach to ETHERMTU */ IEEE80211_LOCK(ic); TAILQ_INSERT_TAIL(&ic->ic_vaps, vap, iv_next); ieee80211_syncflag_locked(ic, IEEE80211_F_WME); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_syncflag_locked(ic, IEEE80211_F_TURBOP); #endif ieee80211_syncflag_locked(ic, IEEE80211_F_PCF); ieee80211_syncflag_locked(ic, IEEE80211_F_BURST); ieee80211_syncflag_ht_locked(ic, IEEE80211_FHT_HT); ieee80211_syncflag_ht_locked(ic, IEEE80211_FHT_USEHT40); IEEE80211_UNLOCK(ic); return 1; } /* * Tear down vap state and reclaim the ifnet. * The driver is assumed to have prepared for * this; e.g. by turning off interrupts for the * underlying device. */ void ieee80211_vap_detach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ifnet *ifp = vap->iv_ifp; CURVNET_SET(ifp->if_vnet); IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s parent %s\n", __func__, ieee80211_opmode_name[vap->iv_opmode], ic->ic_name); /* NB: bpfdetach is called by ether_ifdetach and claims all taps */ ether_ifdetach(ifp); ieee80211_stop(vap); /* * Flush any deferred vap tasks. */ ieee80211_draintask(ic, &vap->iv_nstate_task); ieee80211_draintask(ic, &vap->iv_swbmiss_task); /* XXX band-aid until ifnet handles this for us */ taskqueue_drain(taskqueue_swi, &ifp->if_linktask); IEEE80211_LOCK(ic); KASSERT(vap->iv_state == IEEE80211_S_INIT , ("vap still running")); TAILQ_REMOVE(&ic->ic_vaps, vap, iv_next); ieee80211_syncflag_locked(ic, IEEE80211_F_WME); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_syncflag_locked(ic, IEEE80211_F_TURBOP); #endif ieee80211_syncflag_locked(ic, IEEE80211_F_PCF); ieee80211_syncflag_locked(ic, IEEE80211_F_BURST); ieee80211_syncflag_ht_locked(ic, IEEE80211_FHT_HT); ieee80211_syncflag_ht_locked(ic, IEEE80211_FHT_USEHT40); /* NB: this handles the bpfdetach done below */ ieee80211_syncflag_ext_locked(ic, IEEE80211_FEXT_BPF); if (vap->iv_ifflags & IFF_PROMISC) ieee80211_promisc(vap, false); if (vap->iv_ifflags & IFF_ALLMULTI) ieee80211_allmulti(vap, false); IEEE80211_UNLOCK(ic); ifmedia_removeall(&vap->iv_media); ieee80211_radiotap_vdetach(vap); ieee80211_regdomain_vdetach(vap); ieee80211_scan_vdetach(vap); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_vdetach(vap); #endif ieee80211_ht_vdetach(vap); /* NB: must be before ieee80211_node_vdetach */ ieee80211_proto_vdetach(vap); ieee80211_crypto_vdetach(vap); ieee80211_power_vdetach(vap); ieee80211_node_vdetach(vap); ieee80211_sysctl_vdetach(vap); if_free(ifp); CURVNET_RESTORE(); } /* * Count number of vaps in promisc, and issue promisc on * parent respectively. */ void ieee80211_promisc(struct ieee80211vap *vap, bool on) { struct ieee80211com *ic = vap->iv_ic; /* * XXX the bridge sets PROMISC but we don't want to * enable it on the device, discard here so all the * drivers don't need to special-case it */ if (!(vap->iv_opmode == IEEE80211_M_MONITOR || (vap->iv_opmode == IEEE80211_M_AHDEMO && (vap->iv_caps & IEEE80211_C_TDMA) == 0))) return; IEEE80211_LOCK_ASSERT(ic); if (on) { if (++ic->ic_promisc == 1) ieee80211_runtask(ic, &ic->ic_promisc_task); } else { KASSERT(ic->ic_promisc > 0, ("%s: ic %p not promisc", __func__, ic)); if (--ic->ic_promisc == 0) ieee80211_runtask(ic, &ic->ic_promisc_task); } } /* * Count number of vaps in allmulti, and issue allmulti on * parent respectively. */ void ieee80211_allmulti(struct ieee80211vap *vap, bool on) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); if (on) { if (++ic->ic_allmulti == 1) ieee80211_runtask(ic, &ic->ic_mcast_task); } else { KASSERT(ic->ic_allmulti > 0, ("%s: ic %p not allmulti", __func__, ic)); if (--ic->ic_allmulti == 0) ieee80211_runtask(ic, &ic->ic_mcast_task); } } /* * Synchronize flag bit state in the com structure * according to the state of all vap's. This is used, * for example, to handle state changes via ioctls. */ static void ieee80211_syncflag_locked(struct ieee80211com *ic, int flag) { struct ieee80211vap *vap; int bit; IEEE80211_LOCK_ASSERT(ic); bit = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_flags & flag) { bit = 1; break; } if (bit) ic->ic_flags |= flag; else ic->ic_flags &= ~flag; } void ieee80211_syncflag(struct ieee80211vap *vap, int flag) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); if (flag < 0) { flag = -flag; vap->iv_flags &= ~flag; } else vap->iv_flags |= flag; ieee80211_syncflag_locked(ic, flag); IEEE80211_UNLOCK(ic); } /* * Synchronize flags_ht bit state in the com structure * according to the state of all vap's. This is used, * for example, to handle state changes via ioctls. */ static void ieee80211_syncflag_ht_locked(struct ieee80211com *ic, int flag) { struct ieee80211vap *vap; int bit; IEEE80211_LOCK_ASSERT(ic); bit = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_flags_ht & flag) { bit = 1; break; } if (bit) ic->ic_flags_ht |= flag; else ic->ic_flags_ht &= ~flag; } void ieee80211_syncflag_ht(struct ieee80211vap *vap, int flag) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); if (flag < 0) { flag = -flag; vap->iv_flags_ht &= ~flag; } else vap->iv_flags_ht |= flag; ieee80211_syncflag_ht_locked(ic, flag); IEEE80211_UNLOCK(ic); } /* * Synchronize flags_ext bit state in the com structure * according to the state of all vap's. This is used, * for example, to handle state changes via ioctls. */ static void ieee80211_syncflag_ext_locked(struct ieee80211com *ic, int flag) { struct ieee80211vap *vap; int bit; IEEE80211_LOCK_ASSERT(ic); bit = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_flags_ext & flag) { bit = 1; break; } if (bit) ic->ic_flags_ext |= flag; else ic->ic_flags_ext &= ~flag; } void ieee80211_syncflag_ext(struct ieee80211vap *vap, int flag) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); if (flag < 0) { flag = -flag; vap->iv_flags_ext &= ~flag; } else vap->iv_flags_ext |= flag; ieee80211_syncflag_ext_locked(ic, flag); IEEE80211_UNLOCK(ic); } static __inline int mapgsm(u_int freq, u_int flags) { freq *= 10; if (flags & IEEE80211_CHAN_QUARTER) freq += 5; else if (flags & IEEE80211_CHAN_HALF) freq += 10; else freq += 20; /* NB: there is no 907/20 wide but leave room */ return (freq - 906*10) / 5; } static __inline int mappsb(u_int freq, u_int flags) { return 37 + ((freq * 10) + ((freq % 5) == 2 ? 5 : 0) - 49400) / 5; } /* * Convert MHz frequency to IEEE channel number. */ int ieee80211_mhz2ieee(u_int freq, u_int flags) { #define IS_FREQ_IN_PSB(_freq) ((_freq) > 4940 && (_freq) < 4990) if (flags & IEEE80211_CHAN_GSM) return mapgsm(freq, flags); if (flags & IEEE80211_CHAN_2GHZ) { /* 2GHz band */ if (freq == 2484) return 14; if (freq < 2484) return ((int) freq - 2407) / 5; else return 15 + ((freq - 2512) / 20); } else if (flags & IEEE80211_CHAN_5GHZ) { /* 5Ghz band */ if (freq <= 5000) { /* XXX check regdomain? */ if (IS_FREQ_IN_PSB(freq)) return mappsb(freq, flags); return (freq - 4000) / 5; } else return (freq - 5000) / 5; } else { /* either, guess */ if (freq == 2484) return 14; if (freq < 2484) { if (907 <= freq && freq <= 922) return mapgsm(freq, flags); return ((int) freq - 2407) / 5; } if (freq < 5000) { if (IS_FREQ_IN_PSB(freq)) return mappsb(freq, flags); else if (freq > 4900) return (freq - 4000) / 5; else return 15 + ((freq - 2512) / 20); } return (freq - 5000) / 5; } #undef IS_FREQ_IN_PSB } /* * Convert channel to IEEE channel number. */ int ieee80211_chan2ieee(struct ieee80211com *ic, const struct ieee80211_channel *c) { if (c == NULL) { ic_printf(ic, "invalid channel (NULL)\n"); return 0; /* XXX */ } return (c == IEEE80211_CHAN_ANYC ? IEEE80211_CHAN_ANY : c->ic_ieee); } /* * Convert IEEE channel number to MHz frequency. */ u_int ieee80211_ieee2mhz(u_int chan, u_int flags) { if (flags & IEEE80211_CHAN_GSM) return 907 + 5 * (chan / 10); if (flags & IEEE80211_CHAN_2GHZ) { /* 2GHz band */ if (chan == 14) return 2484; if (chan < 14) return 2407 + chan*5; else return 2512 + ((chan-15)*20); } else if (flags & IEEE80211_CHAN_5GHZ) {/* 5Ghz band */ if (flags & (IEEE80211_CHAN_HALF|IEEE80211_CHAN_QUARTER)) { chan -= 37; return 4940 + chan*5 + (chan % 5 ? 2 : 0); } return 5000 + (chan*5); } else { /* either, guess */ /* XXX can't distinguish PSB+GSM channels */ if (chan == 14) return 2484; if (chan < 14) /* 0-13 */ return 2407 + chan*5; if (chan < 27) /* 15-26 */ return 2512 + ((chan-15)*20); return 5000 + (chan*5); } } /* * Locate a channel given a frequency+flags. We cache * the previous lookup to optimize switching between two * channels--as happens with dynamic turbo. */ struct ieee80211_channel * ieee80211_find_channel(struct ieee80211com *ic, int freq, int flags) { struct ieee80211_channel *c; int i; flags &= IEEE80211_CHAN_ALLTURBO; c = ic->ic_prevchan; if (c != NULL && c->ic_freq == freq && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; /* brute force search */ for (i = 0; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; if (c->ic_freq == freq && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; } return NULL; } /* * Locate a channel given a channel number+flags. We cache * the previous lookup to optimize switching between two * channels--as happens with dynamic turbo. */ struct ieee80211_channel * ieee80211_find_channel_byieee(struct ieee80211com *ic, int ieee, int flags) { struct ieee80211_channel *c; int i; flags &= IEEE80211_CHAN_ALLTURBO; c = ic->ic_prevchan; if (c != NULL && c->ic_ieee == ieee && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; /* brute force search */ for (i = 0; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; if (c->ic_ieee == ieee && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; } return NULL; } /* * Lookup a channel suitable for the given rx status. * * This is used to find a channel for a frame (eg beacon, probe * response) based purely on the received PHY information. * * For now it tries to do it based on R_FREQ / R_IEEE. * This is enough for 11bg and 11a (and thus 11ng/11na) * but it will not be enough for GSM, PSB channels and the * like. It also doesn't know about legacy-turbog and * legacy-turbo modes, which some offload NICs actually * support in weird ways. * * Takes the ic and rxstatus; returns the channel or NULL * if not found. * * XXX TODO: Add support for that when the need arises. */ struct ieee80211_channel * ieee80211_lookup_channel_rxstatus(struct ieee80211vap *vap, const struct ieee80211_rx_stats *rxs) { struct ieee80211com *ic = vap->iv_ic; uint32_t flags; struct ieee80211_channel *c; if (rxs == NULL) return (NULL); /* * Strictly speaking we only use freq for now, * however later on we may wish to just store * the ieee for verification. */ if ((rxs->r_flags & IEEE80211_R_FREQ) == 0) return (NULL); if ((rxs->r_flags & IEEE80211_R_IEEE) == 0) return (NULL); /* * If the rx status contains a valid ieee/freq, then * ensure we populate the correct channel information * in rxchan before passing it up to the scan infrastructure. * Offload NICs will pass up beacons from all channels * during background scans. */ /* Determine a band */ /* XXX should be done by the driver? */ if (rxs->c_freq < 3000) { flags = IEEE80211_CHAN_G; } else { flags = IEEE80211_CHAN_A; } /* Channel lookup */ c = ieee80211_find_channel(ic, rxs->c_freq, flags); IEEE80211_DPRINTF(vap, IEEE80211_MSG_INPUT, "%s: freq=%d, ieee=%d, flags=0x%08x; c=%p\n", __func__, (int) rxs->c_freq, (int) rxs->c_ieee, flags, c); return (c); } static void addmedia(struct ifmedia *media, int caps, int addsta, int mode, int mword) { #define ADD(_ic, _s, _o) \ ifmedia_add(media, \ IFM_MAKEWORD(IFM_IEEE80211, (_s), (_o), 0), 0, NULL) static const u_int mopts[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = IFM_AUTO, [IEEE80211_MODE_11A] = IFM_IEEE80211_11A, [IEEE80211_MODE_11B] = IFM_IEEE80211_11B, [IEEE80211_MODE_11G] = IFM_IEEE80211_11G, [IEEE80211_MODE_FH] = IFM_IEEE80211_FH, [IEEE80211_MODE_TURBO_A] = IFM_IEEE80211_11A|IFM_IEEE80211_TURBO, [IEEE80211_MODE_TURBO_G] = IFM_IEEE80211_11G|IFM_IEEE80211_TURBO, [IEEE80211_MODE_STURBO_A] = IFM_IEEE80211_11A|IFM_IEEE80211_TURBO, [IEEE80211_MODE_HALF] = IFM_IEEE80211_11A, /* XXX */ [IEEE80211_MODE_QUARTER] = IFM_IEEE80211_11A, /* XXX */ [IEEE80211_MODE_11NA] = IFM_IEEE80211_11NA, [IEEE80211_MODE_11NG] = IFM_IEEE80211_11NG, }; u_int mopt; mopt = mopts[mode]; if (addsta) ADD(ic, mword, mopt); /* STA mode has no cap */ if (caps & IEEE80211_C_IBSS) ADD(media, mword, mopt | IFM_IEEE80211_ADHOC); if (caps & IEEE80211_C_HOSTAP) ADD(media, mword, mopt | IFM_IEEE80211_HOSTAP); if (caps & IEEE80211_C_AHDEMO) ADD(media, mword, mopt | IFM_IEEE80211_ADHOC | IFM_FLAG0); if (caps & IEEE80211_C_MONITOR) ADD(media, mword, mopt | IFM_IEEE80211_MONITOR); if (caps & IEEE80211_C_WDS) ADD(media, mword, mopt | IFM_IEEE80211_WDS); if (caps & IEEE80211_C_MBSS) ADD(media, mword, mopt | IFM_IEEE80211_MBSS); #undef ADD } /* * Setup the media data structures according to the channel and * rate tables. */ static int ieee80211_media_setup(struct ieee80211com *ic, struct ifmedia *media, int caps, int addsta, ifm_change_cb_t media_change, ifm_stat_cb_t media_stat) { int i, j, rate, maxrate, mword, r; enum ieee80211_phymode mode; const struct ieee80211_rateset *rs; struct ieee80211_rateset allrates; /* * Fill in media characteristics. */ ifmedia_init(media, 0, media_change, media_stat); maxrate = 0; /* * Add media for legacy operating modes. */ memset(&allrates, 0, sizeof(allrates)); for (mode = IEEE80211_MODE_AUTO; mode < IEEE80211_MODE_11NA; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; addmedia(media, caps, addsta, mode, IFM_AUTO); if (mode == IEEE80211_MODE_AUTO) continue; rs = &ic->ic_sup_rates[mode]; for (i = 0; i < rs->rs_nrates; i++) { rate = rs->rs_rates[i]; mword = ieee80211_rate2media(ic, rate, mode); if (mword == 0) continue; addmedia(media, caps, addsta, mode, mword); /* * Add legacy rate to the collection of all rates. */ r = rate & IEEE80211_RATE_VAL; for (j = 0; j < allrates.rs_nrates; j++) if (allrates.rs_rates[j] == r) break; if (j == allrates.rs_nrates) { /* unique, add to the set */ allrates.rs_rates[j] = r; allrates.rs_nrates++; } rate = (rate & IEEE80211_RATE_VAL) / 2; if (rate > maxrate) maxrate = rate; } } for (i = 0; i < allrates.rs_nrates; i++) { mword = ieee80211_rate2media(ic, allrates.rs_rates[i], IEEE80211_MODE_AUTO); if (mword == 0) continue; /* NB: remove media options from mword */ addmedia(media, caps, addsta, IEEE80211_MODE_AUTO, IFM_SUBTYPE(mword)); } /* * Add HT/11n media. Note that we do not have enough * bits in the media subtype to express the MCS so we * use a "placeholder" media subtype and any fixed MCS * must be specified with a different mechanism. */ for (; mode <= IEEE80211_MODE_11NG; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; addmedia(media, caps, addsta, mode, IFM_AUTO); addmedia(media, caps, addsta, mode, IFM_IEEE80211_MCS); } if (isset(ic->ic_modecaps, IEEE80211_MODE_11NA) || isset(ic->ic_modecaps, IEEE80211_MODE_11NG)) { addmedia(media, caps, addsta, IEEE80211_MODE_AUTO, IFM_IEEE80211_MCS); i = ic->ic_txstream * 8 - 1; if ((ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) && (ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI40)) rate = ieee80211_htrates[i].ht40_rate_400ns; else if ((ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40)) rate = ieee80211_htrates[i].ht40_rate_800ns; else if ((ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI20)) rate = ieee80211_htrates[i].ht20_rate_400ns; else rate = ieee80211_htrates[i].ht20_rate_800ns; if (rate > maxrate) maxrate = rate; } return maxrate; } /* XXX inline or eliminate? */ const struct ieee80211_rateset * ieee80211_get_suprates(struct ieee80211com *ic, const struct ieee80211_channel *c) { /* XXX does this work for 11ng basic rates? */ return &ic->ic_sup_rates[ieee80211_chan2mode(c)]; } void ieee80211_announce(struct ieee80211com *ic) { int i, rate, mword; enum ieee80211_phymode mode; const struct ieee80211_rateset *rs; /* NB: skip AUTO since it has no rates */ for (mode = IEEE80211_MODE_AUTO+1; mode < IEEE80211_MODE_11NA; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; ic_printf(ic, "%s rates: ", ieee80211_phymode_name[mode]); rs = &ic->ic_sup_rates[mode]; for (i = 0; i < rs->rs_nrates; i++) { mword = ieee80211_rate2media(ic, rs->rs_rates[i], mode); if (mword == 0) continue; rate = ieee80211_media2rate(mword); printf("%s%d%sMbps", (i != 0 ? " " : ""), rate / 2, ((rate & 0x1) != 0 ? ".5" : "")); } printf("\n"); } ieee80211_ht_announce(ic); } void ieee80211_announce_channels(struct ieee80211com *ic) { const struct ieee80211_channel *c; char type; int i, cw; printf("Chan Freq CW RegPwr MinPwr MaxPwr\n"); for (i = 0; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; if (IEEE80211_IS_CHAN_ST(c)) type = 'S'; else if (IEEE80211_IS_CHAN_108A(c)) type = 'T'; else if (IEEE80211_IS_CHAN_108G(c)) type = 'G'; else if (IEEE80211_IS_CHAN_HT(c)) type = 'n'; else if (IEEE80211_IS_CHAN_A(c)) type = 'a'; else if (IEEE80211_IS_CHAN_ANYG(c)) type = 'g'; else if (IEEE80211_IS_CHAN_B(c)) type = 'b'; else type = 'f'; if (IEEE80211_IS_CHAN_HT40(c) || IEEE80211_IS_CHAN_TURBO(c)) cw = 40; else if (IEEE80211_IS_CHAN_HALF(c)) cw = 10; else if (IEEE80211_IS_CHAN_QUARTER(c)) cw = 5; else cw = 20; printf("%4d %4d%c %2d%c %6d %4d.%d %4d.%d\n" , c->ic_ieee, c->ic_freq, type , cw , IEEE80211_IS_CHAN_HT40U(c) ? '+' : IEEE80211_IS_CHAN_HT40D(c) ? '-' : ' ' , c->ic_maxregpower , c->ic_minpower / 2, c->ic_minpower & 1 ? 5 : 0 , c->ic_maxpower / 2, c->ic_maxpower & 1 ? 5 : 0 ); } } static int media2mode(const struct ifmedia_entry *ime, uint32_t flags, uint16_t *mode) { switch (IFM_MODE(ime->ifm_media)) { case IFM_IEEE80211_11A: *mode = IEEE80211_MODE_11A; break; case IFM_IEEE80211_11B: *mode = IEEE80211_MODE_11B; break; case IFM_IEEE80211_11G: *mode = IEEE80211_MODE_11G; break; case IFM_IEEE80211_FH: *mode = IEEE80211_MODE_FH; break; case IFM_IEEE80211_11NA: *mode = IEEE80211_MODE_11NA; break; case IFM_IEEE80211_11NG: *mode = IEEE80211_MODE_11NG; break; case IFM_AUTO: *mode = IEEE80211_MODE_AUTO; break; default: return 0; } /* * Turbo mode is an ``option''. * XXX does not apply to AUTO */ if (ime->ifm_media & IFM_IEEE80211_TURBO) { if (*mode == IEEE80211_MODE_11A) { if (flags & IEEE80211_F_TURBOP) *mode = IEEE80211_MODE_TURBO_A; else *mode = IEEE80211_MODE_STURBO_A; } else if (*mode == IEEE80211_MODE_11G) *mode = IEEE80211_MODE_TURBO_G; else return 0; } /* XXX HT40 +/- */ return 1; } /* * Handle a media change request on the vap interface. */ int ieee80211_media_change(struct ifnet *ifp) { struct ieee80211vap *vap = ifp->if_softc; struct ifmedia_entry *ime = vap->iv_media.ifm_cur; uint16_t newmode; if (!media2mode(ime, vap->iv_flags, &newmode)) return EINVAL; if (vap->iv_des_mode != newmode) { vap->iv_des_mode = newmode; /* XXX kick state machine if up+running */ } return 0; } /* * Common code to calculate the media status word * from the operating mode and channel state. */ static int media_status(enum ieee80211_opmode opmode, const struct ieee80211_channel *chan) { int status; status = IFM_IEEE80211; switch (opmode) { case IEEE80211_M_STA: break; case IEEE80211_M_IBSS: status |= IFM_IEEE80211_ADHOC; break; case IEEE80211_M_HOSTAP: status |= IFM_IEEE80211_HOSTAP; break; case IEEE80211_M_MONITOR: status |= IFM_IEEE80211_MONITOR; break; case IEEE80211_M_AHDEMO: status |= IFM_IEEE80211_ADHOC | IFM_FLAG0; break; case IEEE80211_M_WDS: status |= IFM_IEEE80211_WDS; break; case IEEE80211_M_MBSS: status |= IFM_IEEE80211_MBSS; break; } if (IEEE80211_IS_CHAN_HTA(chan)) { status |= IFM_IEEE80211_11NA; } else if (IEEE80211_IS_CHAN_HTG(chan)) { status |= IFM_IEEE80211_11NG; } else if (IEEE80211_IS_CHAN_A(chan)) { status |= IFM_IEEE80211_11A; } else if (IEEE80211_IS_CHAN_B(chan)) { status |= IFM_IEEE80211_11B; } else if (IEEE80211_IS_CHAN_ANYG(chan)) { status |= IFM_IEEE80211_11G; } else if (IEEE80211_IS_CHAN_FHSS(chan)) { status |= IFM_IEEE80211_FH; } /* XXX else complain? */ if (IEEE80211_IS_CHAN_TURBO(chan)) status |= IFM_IEEE80211_TURBO; #if 0 if (IEEE80211_IS_CHAN_HT20(chan)) status |= IFM_IEEE80211_HT20; if (IEEE80211_IS_CHAN_HT40(chan)) status |= IFM_IEEE80211_HT40; #endif return status; } void ieee80211_media_status(struct ifnet *ifp, struct ifmediareq *imr) { struct ieee80211vap *vap = ifp->if_softc; struct ieee80211com *ic = vap->iv_ic; enum ieee80211_phymode mode; imr->ifm_status = IFM_AVALID; /* * NB: use the current channel's mode to lock down a xmit * rate only when running; otherwise we may have a mismatch * in which case the rate will not be convertible. */ if (vap->iv_state == IEEE80211_S_RUN || vap->iv_state == IEEE80211_S_SLEEP) { imr->ifm_status |= IFM_ACTIVE; mode = ieee80211_chan2mode(ic->ic_curchan); } else mode = IEEE80211_MODE_AUTO; imr->ifm_active = media_status(vap->iv_opmode, ic->ic_curchan); /* * Calculate a current rate if possible. */ if (vap->iv_txparms[mode].ucastrate != IEEE80211_FIXED_RATE_NONE) { /* * A fixed rate is set, report that. */ imr->ifm_active |= ieee80211_rate2media(ic, vap->iv_txparms[mode].ucastrate, mode); } else if (vap->iv_opmode == IEEE80211_M_STA) { /* * In station mode report the current transmit rate. */ imr->ifm_active |= ieee80211_rate2media(ic, vap->iv_bss->ni_txrate, mode); } else imr->ifm_active |= IFM_AUTO; if (imr->ifm_status & IFM_ACTIVE) imr->ifm_current = imr->ifm_active; } /* * Set the current phy mode and recalculate the active channel * set based on the available channels for this mode. Also * select a new default/current channel if the current one is * inappropriate for this mode. */ int ieee80211_setmode(struct ieee80211com *ic, enum ieee80211_phymode mode) { /* * Adjust basic rates in 11b/11g supported rate set. * Note that if operating on a hal/quarter rate channel * this is a noop as those rates sets are different * and used instead. */ if (mode == IEEE80211_MODE_11G || mode == IEEE80211_MODE_11B) ieee80211_setbasicrates(&ic->ic_sup_rates[mode], mode); ic->ic_curmode = mode; ieee80211_reset_erp(ic); /* reset ERP state */ return 0; } /* * Return the phy mode for with the specified channel. */ enum ieee80211_phymode ieee80211_chan2mode(const struct ieee80211_channel *chan) { if (IEEE80211_IS_CHAN_HTA(chan)) return IEEE80211_MODE_11NA; else if (IEEE80211_IS_CHAN_HTG(chan)) return IEEE80211_MODE_11NG; else if (IEEE80211_IS_CHAN_108G(chan)) return IEEE80211_MODE_TURBO_G; else if (IEEE80211_IS_CHAN_ST(chan)) return IEEE80211_MODE_STURBO_A; else if (IEEE80211_IS_CHAN_TURBO(chan)) return IEEE80211_MODE_TURBO_A; else if (IEEE80211_IS_CHAN_HALF(chan)) return IEEE80211_MODE_HALF; else if (IEEE80211_IS_CHAN_QUARTER(chan)) return IEEE80211_MODE_QUARTER; else if (IEEE80211_IS_CHAN_A(chan)) return IEEE80211_MODE_11A; else if (IEEE80211_IS_CHAN_ANYG(chan)) return IEEE80211_MODE_11G; else if (IEEE80211_IS_CHAN_B(chan)) return IEEE80211_MODE_11B; else if (IEEE80211_IS_CHAN_FHSS(chan)) return IEEE80211_MODE_FH; /* NB: should not get here */ printf("%s: cannot map channel to mode; freq %u flags 0x%x\n", __func__, chan->ic_freq, chan->ic_flags); return IEEE80211_MODE_11B; } struct ratemedia { u_int match; /* rate + mode */ u_int media; /* if_media rate */ }; static int findmedia(const struct ratemedia rates[], int n, u_int match) { int i; for (i = 0; i < n; i++) if (rates[i].match == match) return rates[i].media; return IFM_AUTO; } /* * Convert IEEE80211 rate value to ifmedia subtype. * Rate is either a legacy rate in units of 0.5Mbps * or an MCS index. */ int ieee80211_rate2media(struct ieee80211com *ic, int rate, enum ieee80211_phymode mode) { static const struct ratemedia rates[] = { { 2 | IFM_IEEE80211_FH, IFM_IEEE80211_FH1 }, { 4 | IFM_IEEE80211_FH, IFM_IEEE80211_FH2 }, { 2 | IFM_IEEE80211_11B, IFM_IEEE80211_DS1 }, { 4 | IFM_IEEE80211_11B, IFM_IEEE80211_DS2 }, { 11 | IFM_IEEE80211_11B, IFM_IEEE80211_DS5 }, { 22 | IFM_IEEE80211_11B, IFM_IEEE80211_DS11 }, { 44 | IFM_IEEE80211_11B, IFM_IEEE80211_DS22 }, { 12 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM6 }, { 18 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM9 }, { 24 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM12 }, { 36 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM18 }, { 48 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM24 }, { 72 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM36 }, { 96 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM48 }, { 108 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM54 }, { 2 | IFM_IEEE80211_11G, IFM_IEEE80211_DS1 }, { 4 | IFM_IEEE80211_11G, IFM_IEEE80211_DS2 }, { 11 | IFM_IEEE80211_11G, IFM_IEEE80211_DS5 }, { 22 | IFM_IEEE80211_11G, IFM_IEEE80211_DS11 }, { 12 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM6 }, { 18 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM9 }, { 24 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM12 }, { 36 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM18 }, { 48 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM24 }, { 72 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM36 }, { 96 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM48 }, { 108 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM54 }, { 6 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM3 }, { 9 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM4 }, { 54 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM27 }, /* NB: OFDM72 doesn't realy exist so we don't handle it */ }; static const struct ratemedia htrates[] = { { 0, IFM_IEEE80211_MCS }, { 1, IFM_IEEE80211_MCS }, { 2, IFM_IEEE80211_MCS }, { 3, IFM_IEEE80211_MCS }, { 4, IFM_IEEE80211_MCS }, { 5, IFM_IEEE80211_MCS }, { 6, IFM_IEEE80211_MCS }, { 7, IFM_IEEE80211_MCS }, { 8, IFM_IEEE80211_MCS }, { 9, IFM_IEEE80211_MCS }, { 10, IFM_IEEE80211_MCS }, { 11, IFM_IEEE80211_MCS }, { 12, IFM_IEEE80211_MCS }, { 13, IFM_IEEE80211_MCS }, { 14, IFM_IEEE80211_MCS }, { 15, IFM_IEEE80211_MCS }, { 16, IFM_IEEE80211_MCS }, { 17, IFM_IEEE80211_MCS }, { 18, IFM_IEEE80211_MCS }, { 19, IFM_IEEE80211_MCS }, { 20, IFM_IEEE80211_MCS }, { 21, IFM_IEEE80211_MCS }, { 22, IFM_IEEE80211_MCS }, { 23, IFM_IEEE80211_MCS }, { 24, IFM_IEEE80211_MCS }, { 25, IFM_IEEE80211_MCS }, { 26, IFM_IEEE80211_MCS }, { 27, IFM_IEEE80211_MCS }, { 28, IFM_IEEE80211_MCS }, { 29, IFM_IEEE80211_MCS }, { 30, IFM_IEEE80211_MCS }, { 31, IFM_IEEE80211_MCS }, { 32, IFM_IEEE80211_MCS }, { 33, IFM_IEEE80211_MCS }, { 34, IFM_IEEE80211_MCS }, { 35, IFM_IEEE80211_MCS }, { 36, IFM_IEEE80211_MCS }, { 37, IFM_IEEE80211_MCS }, { 38, IFM_IEEE80211_MCS }, { 39, IFM_IEEE80211_MCS }, { 40, IFM_IEEE80211_MCS }, { 41, IFM_IEEE80211_MCS }, { 42, IFM_IEEE80211_MCS }, { 43, IFM_IEEE80211_MCS }, { 44, IFM_IEEE80211_MCS }, { 45, IFM_IEEE80211_MCS }, { 46, IFM_IEEE80211_MCS }, { 47, IFM_IEEE80211_MCS }, { 48, IFM_IEEE80211_MCS }, { 49, IFM_IEEE80211_MCS }, { 50, IFM_IEEE80211_MCS }, { 51, IFM_IEEE80211_MCS }, { 52, IFM_IEEE80211_MCS }, { 53, IFM_IEEE80211_MCS }, { 54, IFM_IEEE80211_MCS }, { 55, IFM_IEEE80211_MCS }, { 56, IFM_IEEE80211_MCS }, { 57, IFM_IEEE80211_MCS }, { 58, IFM_IEEE80211_MCS }, { 59, IFM_IEEE80211_MCS }, { 60, IFM_IEEE80211_MCS }, { 61, IFM_IEEE80211_MCS }, { 62, IFM_IEEE80211_MCS }, { 63, IFM_IEEE80211_MCS }, { 64, IFM_IEEE80211_MCS }, { 65, IFM_IEEE80211_MCS }, { 66, IFM_IEEE80211_MCS }, { 67, IFM_IEEE80211_MCS }, { 68, IFM_IEEE80211_MCS }, { 69, IFM_IEEE80211_MCS }, { 70, IFM_IEEE80211_MCS }, { 71, IFM_IEEE80211_MCS }, { 72, IFM_IEEE80211_MCS }, { 73, IFM_IEEE80211_MCS }, { 74, IFM_IEEE80211_MCS }, { 75, IFM_IEEE80211_MCS }, { 76, IFM_IEEE80211_MCS }, }; int m; /* * Check 11n rates first for match as an MCS. */ if (mode == IEEE80211_MODE_11NA) { if (rate & IEEE80211_RATE_MCS) { rate &= ~IEEE80211_RATE_MCS; m = findmedia(htrates, nitems(htrates), rate); if (m != IFM_AUTO) return m | IFM_IEEE80211_11NA; } } else if (mode == IEEE80211_MODE_11NG) { /* NB: 12 is ambiguous, it will be treated as an MCS */ if (rate & IEEE80211_RATE_MCS) { rate &= ~IEEE80211_RATE_MCS; m = findmedia(htrates, nitems(htrates), rate); if (m != IFM_AUTO) return m | IFM_IEEE80211_11NG; } } rate &= IEEE80211_RATE_VAL; switch (mode) { case IEEE80211_MODE_11A: case IEEE80211_MODE_HALF: /* XXX good 'nuf */ case IEEE80211_MODE_QUARTER: case IEEE80211_MODE_11NA: case IEEE80211_MODE_TURBO_A: case IEEE80211_MODE_STURBO_A: return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_11A); case IEEE80211_MODE_11B: return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_11B); case IEEE80211_MODE_FH: return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_FH); case IEEE80211_MODE_AUTO: /* NB: ic may be NULL for some drivers */ if (ic != NULL && ic->ic_phytype == IEEE80211_T_FH) return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_FH); /* NB: hack, 11g matches both 11b+11a rates */ /* fall thru... */ case IEEE80211_MODE_11G: case IEEE80211_MODE_11NG: case IEEE80211_MODE_TURBO_G: return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_11G); } return IFM_AUTO; } int ieee80211_media2rate(int mword) { static const int ieeerates[] = { -1, /* IFM_AUTO */ 0, /* IFM_MANUAL */ 0, /* IFM_NONE */ 2, /* IFM_IEEE80211_FH1 */ 4, /* IFM_IEEE80211_FH2 */ 2, /* IFM_IEEE80211_DS1 */ 4, /* IFM_IEEE80211_DS2 */ 11, /* IFM_IEEE80211_DS5 */ 22, /* IFM_IEEE80211_DS11 */ 44, /* IFM_IEEE80211_DS22 */ 12, /* IFM_IEEE80211_OFDM6 */ 18, /* IFM_IEEE80211_OFDM9 */ 24, /* IFM_IEEE80211_OFDM12 */ 36, /* IFM_IEEE80211_OFDM18 */ 48, /* IFM_IEEE80211_OFDM24 */ 72, /* IFM_IEEE80211_OFDM36 */ 96, /* IFM_IEEE80211_OFDM48 */ 108, /* IFM_IEEE80211_OFDM54 */ 144, /* IFM_IEEE80211_OFDM72 */ 0, /* IFM_IEEE80211_DS354k */ 0, /* IFM_IEEE80211_DS512k */ 6, /* IFM_IEEE80211_OFDM3 */ 9, /* IFM_IEEE80211_OFDM4 */ 54, /* IFM_IEEE80211_OFDM27 */ -1, /* IFM_IEEE80211_MCS */ }; return IFM_SUBTYPE(mword) < nitems(ieeerates) ? ieeerates[IFM_SUBTYPE(mword)] : 0; } /* * The following hash function is adapted from "Hash Functions" by Bob Jenkins * ("Algorithm Alley", Dr. Dobbs Journal, September 1997). */ #define mix(a, b, c) \ do { \ a -= b; a -= c; a ^= (c >> 13); \ b -= c; b -= a; b ^= (a << 8); \ c -= a; c -= b; c ^= (b >> 13); \ a -= b; a -= c; a ^= (c >> 12); \ b -= c; b -= a; b ^= (a << 16); \ c -= a; c -= b; c ^= (b >> 5); \ a -= b; a -= c; a ^= (c >> 3); \ b -= c; b -= a; b ^= (a << 10); \ c -= a; c -= b; c ^= (b >> 15); \ } while (/*CONSTCOND*/0) uint32_t ieee80211_mac_hash(const struct ieee80211com *ic, const uint8_t addr[IEEE80211_ADDR_LEN]) { uint32_t a = 0x9e3779b9, b = 0x9e3779b9, c = ic->ic_hash_key; b += addr[5] << 8; b += addr[4]; a += addr[3] << 24; a += addr[2] << 16; a += addr[1] << 8; a += addr[0]; mix(a, b, c); return c; } #undef mix char ieee80211_channel_type_char(const struct ieee80211_channel *c) { if (IEEE80211_IS_CHAN_ST(c)) return 'S'; if (IEEE80211_IS_CHAN_108A(c)) return 'T'; if (IEEE80211_IS_CHAN_108G(c)) return 'G'; if (IEEE80211_IS_CHAN_HT(c)) return 'n'; if (IEEE80211_IS_CHAN_A(c)) return 'a'; if (IEEE80211_IS_CHAN_ANYG(c)) return 'g'; if (IEEE80211_IS_CHAN_B(c)) return 'b'; return 'f'; } Index: projects/release-pkg/sys/net80211/ieee80211_freebsd.h =================================================================== --- projects/release-pkg/sys/net80211/ieee80211_freebsd.h (revision 297604) +++ projects/release-pkg/sys/net80211/ieee80211_freebsd.h (revision 297605) @@ -1,678 +1,678 @@ /*- * Copyright (c) 2003-2008 Sam Leffler, Errno Consulting * 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 _NET80211_IEEE80211_FREEBSD_H_ #define _NET80211_IEEE80211_FREEBSD_H_ #ifdef _KERNEL #include #include #include #include #include #include #include #include /* * Common state locking definitions. */ typedef struct { char name[16]; /* e.g. "ath0_com_lock" */ struct mtx mtx; } ieee80211_com_lock_t; #define IEEE80211_LOCK_INIT(_ic, _name) do { \ ieee80211_com_lock_t *cl = &(_ic)->ic_comlock; \ snprintf(cl->name, sizeof(cl->name), "%s_com_lock", _name); \ mtx_init(&cl->mtx, cl->name, NULL, MTX_DEF | MTX_RECURSE); \ } while (0) #define IEEE80211_LOCK_OBJ(_ic) (&(_ic)->ic_comlock.mtx) #define IEEE80211_LOCK_DESTROY(_ic) mtx_destroy(IEEE80211_LOCK_OBJ(_ic)) #define IEEE80211_LOCK(_ic) mtx_lock(IEEE80211_LOCK_OBJ(_ic)) #define IEEE80211_UNLOCK(_ic) mtx_unlock(IEEE80211_LOCK_OBJ(_ic)) #define IEEE80211_LOCK_ASSERT(_ic) \ mtx_assert(IEEE80211_LOCK_OBJ(_ic), MA_OWNED) #define IEEE80211_UNLOCK_ASSERT(_ic) \ mtx_assert(IEEE80211_LOCK_OBJ(_ic), MA_NOTOWNED) /* * Transmit lock. * * This is a (mostly) temporary lock designed to serialise all of the * transmission operations throughout the stack. */ typedef struct { char name[16]; /* e.g. "ath0_tx_lock" */ struct mtx mtx; } ieee80211_tx_lock_t; #define IEEE80211_TX_LOCK_INIT(_ic, _name) do { \ ieee80211_tx_lock_t *cl = &(_ic)->ic_txlock; \ snprintf(cl->name, sizeof(cl->name), "%s_tx_lock", _name); \ mtx_init(&cl->mtx, cl->name, NULL, MTX_DEF); \ } while (0) #define IEEE80211_TX_LOCK_OBJ(_ic) (&(_ic)->ic_txlock.mtx) #define IEEE80211_TX_LOCK_DESTROY(_ic) mtx_destroy(IEEE80211_TX_LOCK_OBJ(_ic)) #define IEEE80211_TX_LOCK(_ic) mtx_lock(IEEE80211_TX_LOCK_OBJ(_ic)) #define IEEE80211_TX_UNLOCK(_ic) mtx_unlock(IEEE80211_TX_LOCK_OBJ(_ic)) #define IEEE80211_TX_LOCK_ASSERT(_ic) \ mtx_assert(IEEE80211_TX_LOCK_OBJ(_ic), MA_OWNED) #define IEEE80211_TX_UNLOCK_ASSERT(_ic) \ mtx_assert(IEEE80211_TX_LOCK_OBJ(_ic), MA_NOTOWNED) /* * Node locking definitions. */ typedef struct { char name[16]; /* e.g. "ath0_node_lock" */ struct mtx mtx; } ieee80211_node_lock_t; #define IEEE80211_NODE_LOCK_INIT(_nt, _name) do { \ ieee80211_node_lock_t *nl = &(_nt)->nt_nodelock; \ snprintf(nl->name, sizeof(nl->name), "%s_node_lock", _name); \ mtx_init(&nl->mtx, nl->name, NULL, MTX_DEF | MTX_RECURSE); \ } while (0) #define IEEE80211_NODE_LOCK_OBJ(_nt) (&(_nt)->nt_nodelock.mtx) #define IEEE80211_NODE_LOCK_DESTROY(_nt) \ mtx_destroy(IEEE80211_NODE_LOCK_OBJ(_nt)) #define IEEE80211_NODE_LOCK(_nt) \ mtx_lock(IEEE80211_NODE_LOCK_OBJ(_nt)) #define IEEE80211_NODE_IS_LOCKED(_nt) \ mtx_owned(IEEE80211_NODE_LOCK_OBJ(_nt)) #define IEEE80211_NODE_UNLOCK(_nt) \ mtx_unlock(IEEE80211_NODE_LOCK_OBJ(_nt)) #define IEEE80211_NODE_LOCK_ASSERT(_nt) \ mtx_assert(IEEE80211_NODE_LOCK_OBJ(_nt), MA_OWNED) /* * Node table iteration locking definitions; this protects the * scan generation # used to iterate over the station table * while grabbing+releasing the node lock. */ typedef struct { char name[16]; /* e.g. "ath0_scan_lock" */ struct mtx mtx; } ieee80211_scan_lock_t; #define IEEE80211_NODE_ITERATE_LOCK_INIT(_nt, _name) do { \ ieee80211_scan_lock_t *sl = &(_nt)->nt_scanlock; \ snprintf(sl->name, sizeof(sl->name), "%s_scan_lock", _name); \ mtx_init(&sl->mtx, sl->name, NULL, MTX_DEF); \ } while (0) #define IEEE80211_NODE_ITERATE_LOCK_OBJ(_nt) (&(_nt)->nt_scanlock.mtx) #define IEEE80211_NODE_ITERATE_LOCK_DESTROY(_nt) \ mtx_destroy(IEEE80211_NODE_ITERATE_LOCK_OBJ(_nt)) #define IEEE80211_NODE_ITERATE_LOCK(_nt) \ mtx_lock(IEEE80211_NODE_ITERATE_LOCK_OBJ(_nt)) #define IEEE80211_NODE_ITERATE_UNLOCK(_nt) \ mtx_unlock(IEEE80211_NODE_ITERATE_LOCK_OBJ(_nt)) /* * Power-save queue definitions. */ typedef struct mtx ieee80211_psq_lock_t; #define IEEE80211_PSQ_INIT(_psq, _name) \ mtx_init(&(_psq)->psq_lock, _name, "802.11 ps q", MTX_DEF) #define IEEE80211_PSQ_DESTROY(_psq) mtx_destroy(&(_psq)->psq_lock) #define IEEE80211_PSQ_LOCK(_psq) mtx_lock(&(_psq)->psq_lock) #define IEEE80211_PSQ_UNLOCK(_psq) mtx_unlock(&(_psq)->psq_lock) #ifndef IF_PREPEND_LIST #define _IF_PREPEND_LIST(ifq, mhead, mtail, mcount) do { \ (mtail)->m_nextpkt = (ifq)->ifq_head; \ if ((ifq)->ifq_tail == NULL) \ (ifq)->ifq_tail = (mtail); \ (ifq)->ifq_head = (mhead); \ (ifq)->ifq_len += (mcount); \ } while (0) #define IF_PREPEND_LIST(ifq, mhead, mtail, mcount) do { \ IF_LOCK(ifq); \ _IF_PREPEND_LIST(ifq, mhead, mtail, mcount); \ IF_UNLOCK(ifq); \ } while (0) #endif /* IF_PREPEND_LIST */ /* * Age queue definitions. */ typedef struct mtx ieee80211_ageq_lock_t; #define IEEE80211_AGEQ_INIT(_aq, _name) \ mtx_init(&(_aq)->aq_lock, _name, "802.11 age q", MTX_DEF) #define IEEE80211_AGEQ_DESTROY(_aq) mtx_destroy(&(_aq)->aq_lock) #define IEEE80211_AGEQ_LOCK(_aq) mtx_lock(&(_aq)->aq_lock) #define IEEE80211_AGEQ_UNLOCK(_aq) mtx_unlock(&(_aq)->aq_lock) /* * 802.1x MAC ACL database locking definitions. */ typedef struct mtx acl_lock_t; #define ACL_LOCK_INIT(_as, _name) \ mtx_init(&(_as)->as_lock, _name, "802.11 ACL", MTX_DEF) #define ACL_LOCK_DESTROY(_as) mtx_destroy(&(_as)->as_lock) #define ACL_LOCK(_as) mtx_lock(&(_as)->as_lock) #define ACL_UNLOCK(_as) mtx_unlock(&(_as)->as_lock) #define ACL_LOCK_ASSERT(_as) \ mtx_assert((&(_as)->as_lock), MA_OWNED) /* * Scan table definitions. */ typedef struct mtx ieee80211_scan_table_lock_t; #define IEEE80211_SCAN_TABLE_LOCK_INIT(_st, _name) \ mtx_init(&(_st)->st_lock, _name, "802.11 scan table", MTX_DEF) #define IEEE80211_SCAN_TABLE_LOCK_DESTROY(_st) mtx_destroy(&(_st)->st_lock) #define IEEE80211_SCAN_TABLE_LOCK(_st) mtx_lock(&(_st)->st_lock) #define IEEE80211_SCAN_TABLE_UNLOCK(_st) mtx_unlock(&(_st)->st_lock) typedef struct mtx ieee80211_scan_iter_lock_t; #define IEEE80211_SCAN_ITER_LOCK_INIT(_st, _name) \ mtx_init(&(_st)->st_scanlock, _name, "802.11 scangen", MTX_DEF) #define IEEE80211_SCAN_ITER_LOCK_DESTROY(_st) mtx_destroy(&(_st)->st_scanlock) #define IEEE80211_SCAN_ITER_LOCK(_st) mtx_lock(&(_st)->st_scanlock) #define IEEE80211_SCAN_ITER_UNLOCK(_st) mtx_unlock(&(_st)->st_scanlock) /* * Mesh node/routing definitions. */ typedef struct mtx ieee80211_rte_lock_t; #define MESH_RT_ENTRY_LOCK_INIT(_rt, _name) \ mtx_init(&(rt)->rt_lock, _name, "802.11s route entry", MTX_DEF) #define MESH_RT_ENTRY_LOCK_DESTROY(_rt) \ mtx_destroy(&(_rt)->rt_lock) #define MESH_RT_ENTRY_LOCK(rt) mtx_lock(&(rt)->rt_lock) #define MESH_RT_ENTRY_LOCK_ASSERT(rt) mtx_assert(&(rt)->rt_lock, MA_OWNED) #define MESH_RT_ENTRY_UNLOCK(rt) mtx_unlock(&(rt)->rt_lock) typedef struct mtx ieee80211_rt_lock_t; #define MESH_RT_LOCK(ms) mtx_lock(&(ms)->ms_rt_lock) #define MESH_RT_LOCK_ASSERT(ms) mtx_assert(&(ms)->ms_rt_lock, MA_OWNED) #define MESH_RT_UNLOCK(ms) mtx_unlock(&(ms)->ms_rt_lock) #define MESH_RT_LOCK_INIT(ms, name) \ mtx_init(&(ms)->ms_rt_lock, name, "802.11s routing table", MTX_DEF) #define MESH_RT_LOCK_DESTROY(ms) \ mtx_destroy(&(ms)->ms_rt_lock) /* * Node reference counting definitions. * * ieee80211_node_initref initialize the reference count to 1 * ieee80211_node_incref add a reference * ieee80211_node_decref remove a reference * ieee80211_node_dectestref remove a reference and return 1 if this * is the last reference, otherwise 0 * ieee80211_node_refcnt reference count for printing (only) */ #include #define ieee80211_node_initref(_ni) \ do { ((_ni)->ni_refcnt = 1); } while (0) #define ieee80211_node_incref(_ni) \ atomic_add_int(&(_ni)->ni_refcnt, 1) #define ieee80211_node_decref(_ni) \ atomic_subtract_int(&(_ni)->ni_refcnt, 1) struct ieee80211_node; int ieee80211_node_dectestref(struct ieee80211_node *ni); #define ieee80211_node_refcnt(_ni) (_ni)->ni_refcnt struct ifqueue; struct ieee80211vap; void ieee80211_drain_ifq(struct ifqueue *); void ieee80211_flush_ifq(struct ifqueue *, struct ieee80211vap *); void ieee80211_vap_destroy(struct ieee80211vap *); #define IFNET_IS_UP_RUNNING(_ifp) \ (((_ifp)->if_flags & IFF_UP) && \ ((_ifp)->if_drv_flags & IFF_DRV_RUNNING)) /* XXX TODO: cap these at 1, as hz may not be 1000 */ #define msecs_to_ticks(ms) (((ms)*hz)/1000) #define ticks_to_msecs(t) (1000*(t) / hz) #define ticks_to_secs(t) ((t) / hz) #define ieee80211_time_after(a,b) ((long)(b) - (long)(a) < 0) #define ieee80211_time_before(a,b) ieee80211_time_after(b,a) #define ieee80211_time_after_eq(a,b) ((long)(a) - (long)(b) >= 0) #define ieee80211_time_before_eq(a,b) ieee80211_time_after_eq(b,a) struct mbuf *ieee80211_getmgtframe(uint8_t **frm, int headroom, int pktlen); /* tx path usage */ #define M_ENCAP M_PROTO1 /* 802.11 encap done */ #define M_EAPOL M_PROTO3 /* PAE/EAPOL frame */ #define M_PWR_SAV M_PROTO4 /* bypass PS handling */ #define M_MORE_DATA M_PROTO5 /* more data frames to follow */ -#define M_FF M_PROTO6 /* fast frame */ +#define M_FF M_PROTO6 /* fast frame / A-MSDU */ #define M_TXCB M_PROTO7 /* do tx complete callback */ #define M_AMPDU_MPDU M_PROTO8 /* ok for A-MPDU aggregation */ #define M_FRAG M_PROTO9 /* frame fragmentation */ #define M_FIRSTFRAG M_PROTO10 /* first frame fragment */ #define M_LASTFRAG M_PROTO11 /* last frame fragment */ #define M_80211_TX \ (M_ENCAP|M_EAPOL|M_PWR_SAV|M_MORE_DATA|M_FF|M_TXCB| \ M_AMPDU_MPDU|M_FRAG|M_FIRSTFRAG|M_LASTFRAG) /* rx path usage */ #define M_AMPDU M_PROTO1 /* A-MPDU subframe */ #define M_WEP M_PROTO2 /* WEP done by hardware */ #if 0 #define M_AMPDU_MPDU M_PROTO8 /* A-MPDU re-order done */ #endif #define M_80211_RX (M_AMPDU|M_WEP|M_AMPDU_MPDU) #define IEEE80211_MBUF_TX_FLAG_BITS \ M_FLAG_BITS \ "\15M_ENCAP\17M_EAPOL\20M_PWR_SAV\21M_MORE_DATA\22M_FF\23M_TXCB" \ "\24M_AMPDU_MPDU\25M_FRAG\26M_FIRSTFRAG\27M_LASTFRAG" #define IEEE80211_MBUF_RX_FLAG_BITS \ M_FLAG_BITS \ "\15M_AMPDU\16M_WEP\24M_AMPDU_MPDU" /* * Store WME access control bits in the vlan tag. * This is safe since it's done after the packet is classified * (where we use any previous tag) and because it's passed * directly in to the driver and there's no chance someone * else will clobber them on us. */ #define M_WME_SETAC(m, ac) \ ((m)->m_pkthdr.ether_vtag = (ac)) #define M_WME_GETAC(m) ((m)->m_pkthdr.ether_vtag) /* * Mbufs on the power save queue are tagged with an age and * timed out. We reuse the hardware checksum field in the * mbuf packet header to store this data. */ #define M_AGE_SET(m,v) (m->m_pkthdr.csum_data = v) #define M_AGE_GET(m) (m->m_pkthdr.csum_data) #define M_AGE_SUB(m,adj) (m->m_pkthdr.csum_data -= adj) /* * Store the sequence number. */ #define M_SEQNO_SET(m, seqno) \ ((m)->m_pkthdr.tso_segsz = (seqno)) #define M_SEQNO_GET(m) ((m)->m_pkthdr.tso_segsz) #define MTAG_ABI_NET80211 1132948340 /* net80211 ABI */ struct ieee80211_cb { void (*func)(struct ieee80211_node *, void *, int status); void *arg; }; #define NET80211_TAG_CALLBACK 0 /* xmit complete callback */ int ieee80211_add_callback(struct mbuf *m, void (*func)(struct ieee80211_node *, void *, int), void *arg); void ieee80211_process_callback(struct ieee80211_node *, struct mbuf *, int); #define NET80211_TAG_XMIT_PARAMS 1 /* See below; this is after the bpf_params definition */ #define NET80211_TAG_RECV_PARAMS 2 struct ieee80211com; int ieee80211_parent_xmitpkt(struct ieee80211com *, struct mbuf *); int ieee80211_vap_xmitpkt(struct ieee80211vap *, struct mbuf *); void get_random_bytes(void *, size_t); void ieee80211_sysctl_attach(struct ieee80211com *); void ieee80211_sysctl_detach(struct ieee80211com *); void ieee80211_sysctl_vattach(struct ieee80211vap *); void ieee80211_sysctl_vdetach(struct ieee80211vap *); SYSCTL_DECL(_net_wlan); int ieee80211_sysctl_msecs_ticks(SYSCTL_HANDLER_ARGS); void ieee80211_load_module(const char *); /* * A "policy module" is an adjunct module to net80211 that provides * functionality that typically includes policy decisions. This * modularity enables extensibility and vendor-supplied functionality. */ #define _IEEE80211_POLICY_MODULE(policy, name, version) \ typedef void (*policy##_setup)(int); \ SET_DECLARE(policy##_set, policy##_setup); \ static int \ wlan_##name##_modevent(module_t mod, int type, void *unused) \ { \ policy##_setup * const *iter, f; \ switch (type) { \ case MOD_LOAD: \ SET_FOREACH(iter, policy##_set) { \ f = (void*) *iter; \ f(type); \ } \ return 0; \ case MOD_UNLOAD: \ case MOD_QUIESCE: \ if (nrefs) { \ printf("wlan_" #name ": still in use " \ "(%u dynamic refs)\n", nrefs); \ return EBUSY; \ } \ if (type == MOD_UNLOAD) { \ SET_FOREACH(iter, policy##_set) { \ f = (void*) *iter; \ f(type); \ } \ } \ return 0; \ } \ return EINVAL; \ } \ static moduledata_t name##_mod = { \ "wlan_" #name, \ wlan_##name##_modevent, \ 0 \ }; \ DECLARE_MODULE(wlan_##name, name##_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);\ MODULE_VERSION(wlan_##name, version); \ MODULE_DEPEND(wlan_##name, wlan, 1, 1, 1) /* * Crypto modules implement cipher support. */ #define IEEE80211_CRYPTO_MODULE(name, version) \ _IEEE80211_POLICY_MODULE(crypto, name, version); \ static void \ name##_modevent(int type) \ { \ if (type == MOD_LOAD) \ ieee80211_crypto_register(&name); \ else \ ieee80211_crypto_unregister(&name); \ } \ TEXT_SET(crypto##_set, name##_modevent) /* * Scanner modules provide scanning policy. */ #define IEEE80211_SCANNER_MODULE(name, version) \ _IEEE80211_POLICY_MODULE(scanner, name, version) #define IEEE80211_SCANNER_ALG(name, alg, v) \ static void \ name##_modevent(int type) \ { \ if (type == MOD_LOAD) \ ieee80211_scanner_register(alg, &v); \ else \ ieee80211_scanner_unregister(alg, &v); \ } \ TEXT_SET(scanner_set, name##_modevent); \ /* * ACL modules implement acl policy. */ #define IEEE80211_ACL_MODULE(name, alg, version) \ _IEEE80211_POLICY_MODULE(acl, name, version); \ static void \ alg##_modevent(int type) \ { \ if (type == MOD_LOAD) \ ieee80211_aclator_register(&alg); \ else \ ieee80211_aclator_unregister(&alg); \ } \ TEXT_SET(acl_set, alg##_modevent); \ /* * Authenticator modules handle 802.1x/WPA authentication. */ #define IEEE80211_AUTH_MODULE(name, version) \ _IEEE80211_POLICY_MODULE(auth, name, version) #define IEEE80211_AUTH_ALG(name, alg, v) \ static void \ name##_modevent(int type) \ { \ if (type == MOD_LOAD) \ ieee80211_authenticator_register(alg, &v); \ else \ ieee80211_authenticator_unregister(alg); \ } \ TEXT_SET(auth_set, name##_modevent) /* * Rate control modules provide tx rate control support. */ #define IEEE80211_RATECTL_MODULE(alg, version) \ _IEEE80211_POLICY_MODULE(ratectl, alg, version); \ #define IEEE80211_RATECTL_ALG(name, alg, v) \ static void \ alg##_modevent(int type) \ { \ if (type == MOD_LOAD) \ ieee80211_ratectl_register(alg, &v); \ else \ ieee80211_ratectl_unregister(alg); \ } \ TEXT_SET(ratectl##_set, alg##_modevent) struct ieee80211req; typedef int ieee80211_ioctl_getfunc(struct ieee80211vap *, struct ieee80211req *); SET_DECLARE(ieee80211_ioctl_getset, ieee80211_ioctl_getfunc); #define IEEE80211_IOCTL_GET(_name, _get) TEXT_SET(ieee80211_ioctl_getset, _get) typedef int ieee80211_ioctl_setfunc(struct ieee80211vap *, struct ieee80211req *); SET_DECLARE(ieee80211_ioctl_setset, ieee80211_ioctl_setfunc); #define IEEE80211_IOCTL_SET(_name, _set) TEXT_SET(ieee80211_ioctl_setset, _set) #endif /* _KERNEL */ /* XXX this stuff belongs elsewhere */ /* * Message formats for messages from the net80211 layer to user * applications via the routing socket. These messages are appended * to an if_announcemsghdr structure. */ struct ieee80211_join_event { uint8_t iev_addr[6]; }; struct ieee80211_leave_event { uint8_t iev_addr[6]; }; struct ieee80211_replay_event { uint8_t iev_src[6]; /* src MAC */ uint8_t iev_dst[6]; /* dst MAC */ uint8_t iev_cipher; /* cipher type */ uint8_t iev_keyix; /* key id/index */ uint64_t iev_keyrsc; /* RSC from key */ uint64_t iev_rsc; /* RSC from frame */ }; struct ieee80211_michael_event { uint8_t iev_src[6]; /* src MAC */ uint8_t iev_dst[6]; /* dst MAC */ uint8_t iev_cipher; /* cipher type */ uint8_t iev_keyix; /* key id/index */ }; struct ieee80211_wds_event { uint8_t iev_addr[6]; }; struct ieee80211_csa_event { uint32_t iev_flags; /* channel flags */ uint16_t iev_freq; /* setting in Mhz */ uint8_t iev_ieee; /* IEEE channel number */ uint8_t iev_mode; /* CSA mode */ uint8_t iev_count; /* CSA count */ }; struct ieee80211_cac_event { uint32_t iev_flags; /* channel flags */ uint16_t iev_freq; /* setting in Mhz */ uint8_t iev_ieee; /* IEEE channel number */ /* XXX timestamp? */ uint8_t iev_type; /* IEEE80211_NOTIFY_CAC_* */ }; struct ieee80211_radar_event { uint32_t iev_flags; /* channel flags */ uint16_t iev_freq; /* setting in Mhz */ uint8_t iev_ieee; /* IEEE channel number */ /* XXX timestamp? */ }; struct ieee80211_auth_event { uint8_t iev_addr[6]; }; struct ieee80211_deauth_event { uint8_t iev_addr[6]; }; struct ieee80211_country_event { uint8_t iev_addr[6]; uint8_t iev_cc[2]; /* ISO country code */ }; struct ieee80211_radio_event { uint8_t iev_state; /* 1 on, 0 off */ }; #define RTM_IEEE80211_ASSOC 100 /* station associate (bss mode) */ #define RTM_IEEE80211_REASSOC 101 /* station re-associate (bss mode) */ #define RTM_IEEE80211_DISASSOC 102 /* station disassociate (bss mode) */ #define RTM_IEEE80211_JOIN 103 /* station join (ap mode) */ #define RTM_IEEE80211_LEAVE 104 /* station leave (ap mode) */ #define RTM_IEEE80211_SCAN 105 /* scan complete, results available */ #define RTM_IEEE80211_REPLAY 106 /* sequence counter replay detected */ #define RTM_IEEE80211_MICHAEL 107 /* Michael MIC failure detected */ #define RTM_IEEE80211_REJOIN 108 /* station re-associate (ap mode) */ #define RTM_IEEE80211_WDS 109 /* WDS discovery (ap mode) */ #define RTM_IEEE80211_CSA 110 /* Channel Switch Announcement event */ #define RTM_IEEE80211_RADAR 111 /* radar event */ #define RTM_IEEE80211_CAC 112 /* Channel Availability Check event */ #define RTM_IEEE80211_DEAUTH 113 /* station deauthenticate */ #define RTM_IEEE80211_AUTH 114 /* station authenticate (ap mode) */ #define RTM_IEEE80211_COUNTRY 115 /* discovered country code (sta mode) */ #define RTM_IEEE80211_RADIO 116 /* RF kill switch state change */ /* * Structure prepended to raw packets sent through the bpf * interface when set to DLT_IEEE802_11_RADIO. This allows * user applications to specify pretty much everything in * an Atheros tx descriptor. XXX need to generalize. * * XXX cannot be more than 14 bytes as it is copied to a sockaddr's * XXX sa_data area. */ struct ieee80211_bpf_params { uint8_t ibp_vers; /* version */ #define IEEE80211_BPF_VERSION 0 uint8_t ibp_len; /* header length in bytes */ uint8_t ibp_flags; #define IEEE80211_BPF_SHORTPRE 0x01 /* tx with short preamble */ #define IEEE80211_BPF_NOACK 0x02 /* tx with no ack */ #define IEEE80211_BPF_CRYPTO 0x04 /* tx with h/w encryption */ #define IEEE80211_BPF_FCS 0x10 /* frame incldues FCS */ #define IEEE80211_BPF_DATAPAD 0x20 /* frame includes data padding */ #define IEEE80211_BPF_RTS 0x40 /* tx with RTS/CTS */ #define IEEE80211_BPF_CTS 0x80 /* tx with CTS only */ uint8_t ibp_pri; /* WME/WMM AC+tx antenna */ uint8_t ibp_try0; /* series 1 try count */ uint8_t ibp_rate0; /* series 1 IEEE tx rate */ uint8_t ibp_power; /* tx power (device units) */ uint8_t ibp_ctsrate; /* IEEE tx rate for CTS */ uint8_t ibp_try1; /* series 2 try count */ uint8_t ibp_rate1; /* series 2 IEEE tx rate */ uint8_t ibp_try2; /* series 3 try count */ uint8_t ibp_rate2; /* series 3 IEEE tx rate */ uint8_t ibp_try3; /* series 4 try count */ uint8_t ibp_rate3; /* series 4 IEEE tx rate */ }; #ifdef _KERNEL struct ieee80211_tx_params { struct ieee80211_bpf_params params; }; int ieee80211_add_xmit_params(struct mbuf *m, const struct ieee80211_bpf_params *); int ieee80211_get_xmit_params(struct mbuf *m, struct ieee80211_bpf_params *); #define IEEE80211_MAX_CHAINS 3 #define IEEE80211_MAX_EVM_PILOTS 6 #define IEEE80211_R_NF 0x0000001 /* global NF value valid */ #define IEEE80211_R_RSSI 0x0000002 /* global RSSI value valid */ #define IEEE80211_R_C_CHAIN 0x0000004 /* RX chain count valid */ #define IEEE80211_R_C_NF 0x0000008 /* per-chain NF value valid */ #define IEEE80211_R_C_RSSI 0x0000010 /* per-chain RSSI value valid */ #define IEEE80211_R_C_EVM 0x0000020 /* per-chain EVM valid */ #define IEEE80211_R_C_HT40 0x0000040 /* RX'ed packet is 40mhz, pilots 4,5 valid */ #define IEEE80211_R_FREQ 0x0000080 /* Freq value populated, MHz */ #define IEEE80211_R_IEEE 0x0000100 /* IEEE value populated */ #define IEEE80211_R_BAND 0x0000200 /* Frequency band populated */ struct ieee80211_rx_stats { uint32_t r_flags; /* IEEE80211_R_* flags */ uint8_t c_chain; /* number of RX chains involved */ int16_t c_nf_ctl[IEEE80211_MAX_CHAINS]; /* per-chain NF */ int16_t c_nf_ext[IEEE80211_MAX_CHAINS]; /* per-chain NF */ int16_t c_rssi_ctl[IEEE80211_MAX_CHAINS]; /* per-chain RSSI */ int16_t c_rssi_ext[IEEE80211_MAX_CHAINS]; /* per-chain RSSI */ uint8_t nf; /* global NF */ uint8_t rssi; /* global RSSI */ uint8_t evm[IEEE80211_MAX_CHAINS][IEEE80211_MAX_EVM_PILOTS]; /* per-chain, per-pilot EVM values */ uint16_t c_freq; uint8_t c_ieee; }; struct ieee80211_rx_params { struct ieee80211_rx_stats params; }; int ieee80211_add_rx_params(struct mbuf *m, const struct ieee80211_rx_stats *rxs); int ieee80211_get_rx_params(struct mbuf *m, struct ieee80211_rx_stats *rxs); #endif /* _KERNEL */ /* * Malloc API. Other BSD operating systems have slightly * different malloc/free namings (eg DragonflyBSD.) */ #define IEEE80211_MALLOC malloc #define IEEE80211_FREE free /* XXX TODO: get rid of WAITOK, fix all the users of it? */ #define IEEE80211_M_NOWAIT M_NOWAIT #define IEEE80211_M_WAITOK M_WAITOK #define IEEE80211_M_ZERO M_ZERO /* XXX TODO: the type fields */ #endif /* _NET80211_IEEE80211_FREEBSD_H_ */ Index: projects/release-pkg/sys/net80211/ieee80211_hostap.c =================================================================== --- projects/release-pkg/sys/net80211/ieee80211_hostap.c (revision 297604) +++ projects/release-pkg/sys/net80211/ieee80211_hostap.c (revision 297605) @@ -1,2322 +1,2326 @@ /*- * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting * 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 #ifdef __FreeBSD__ __FBSDID("$FreeBSD$"); #endif /* * IEEE 802.11 HOSTAP mode support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #include #define IEEE80211_RATE2MBS(r) (((r) & IEEE80211_RATE_VAL) / 2) static void hostap_vattach(struct ieee80211vap *); static int hostap_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int hostap_input(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *, int rssi, int nf); static void hostap_deliver_data(struct ieee80211vap *, struct ieee80211_node *, struct mbuf *); static void hostap_recv_mgmt(struct ieee80211_node *, struct mbuf *, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf); static void hostap_recv_ctl(struct ieee80211_node *, struct mbuf *, int); void ieee80211_hostap_attach(struct ieee80211com *ic) { ic->ic_vattach[IEEE80211_M_HOSTAP] = hostap_vattach; } void ieee80211_hostap_detach(struct ieee80211com *ic) { } static void hostap_vdetach(struct ieee80211vap *vap) { } static void hostap_vattach(struct ieee80211vap *vap) { vap->iv_newstate = hostap_newstate; vap->iv_input = hostap_input; vap->iv_recv_mgmt = hostap_recv_mgmt; vap->iv_recv_ctl = hostap_recv_ctl; vap->iv_opdetach = hostap_vdetach; vap->iv_deliver_data = hostap_deliver_data; vap->iv_recv_pspoll = ieee80211_recv_pspoll; } static void sta_disassoc(void *arg, struct ieee80211_node *ni) { struct ieee80211vap *vap = arg; if (ni->ni_vap == vap && ni->ni_associd != 0) { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DISASSOC, IEEE80211_REASON_ASSOC_LEAVE); ieee80211_node_leave(ni); } } static void sta_csa(void *arg, struct ieee80211_node *ni) { struct ieee80211vap *vap = arg; if (ni->ni_vap == vap && ni->ni_associd != 0) if (ni->ni_inact > vap->iv_inact_init) { ni->ni_inact = vap->iv_inact_init; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact %u", __func__, ni->ni_inact); } } static void sta_drop(void *arg, struct ieee80211_node *ni) { struct ieee80211vap *vap = arg; if (ni->ni_vap == vap && ni->ni_associd != 0) ieee80211_node_leave(ni); } /* * Does a channel change require associated stations to re-associate * so protocol state is correct. This is used when doing CSA across * bands or similar (e.g. HT -> legacy). */ static int isbandchange(struct ieee80211com *ic) { return ((ic->ic_bsschan->ic_flags ^ ic->ic_csa_newchan->ic_flags) & (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER | IEEE80211_CHAN_HT)) != 0; } /* * IEEE80211_M_HOSTAP vap state machine handler. */ static int hostap_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; enum ieee80211_state ostate; IEEE80211_LOCK_ASSERT(ic); ostate = vap->iv_state; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (%d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg); vap->iv_state = nstate; /* state transition */ if (ostate != IEEE80211_S_SCAN) ieee80211_cancel_scan(vap); /* background scan */ switch (nstate) { case IEEE80211_S_INIT: switch (ostate) { case IEEE80211_S_SCAN: ieee80211_cancel_scan(vap); break; case IEEE80211_S_CAC: ieee80211_dfs_cac_stop(vap); break; case IEEE80211_S_RUN: ieee80211_iterate_nodes(&ic->ic_sta, sta_disassoc, vap); break; default: break; } if (ostate != IEEE80211_S_INIT) { /* NB: optimize INIT -> INIT case */ ieee80211_reset_bss(vap); } if (vap->iv_auth->ia_detach != NULL) vap->iv_auth->ia_detach(vap); break; case IEEE80211_S_SCAN: switch (ostate) { case IEEE80211_S_CSA: case IEEE80211_S_RUN: ieee80211_iterate_nodes(&ic->ic_sta, sta_disassoc, vap); /* * Clear overlapping BSS state; the beacon frame * will be reconstructed on transition to the RUN * state and the timeout routines check if the flag * is set before doing anything so this is sufficient. */ ic->ic_flags_ext &= ~IEEE80211_FEXT_NONERP_PR; ic->ic_flags_ht &= ~IEEE80211_FHT_NONHT_PR; /* fall thru... */ case IEEE80211_S_CAC: /* * NB: We may get here because of a manual channel * change in which case we need to stop CAC * XXX no need to stop if ostate RUN but it's ok */ ieee80211_dfs_cac_stop(vap); /* fall thru... */ case IEEE80211_S_INIT: if (vap->iv_des_chan != IEEE80211_CHAN_ANYC && !IEEE80211_IS_CHAN_RADAR(vap->iv_des_chan)) { /* * Already have a channel; bypass the * scan and startup immediately. * ieee80211_create_ibss will call back to * move us to RUN state. */ ieee80211_create_ibss(vap, vap->iv_des_chan); break; } /* * Initiate a scan. We can come here as a result * of an IEEE80211_IOC_SCAN_REQ too in which case * the vap will be marked with IEEE80211_FEXT_SCANREQ * and the scan request parameters will be present * in iv_scanreq. Otherwise we do the default. */ if (vap->iv_flags_ext & IEEE80211_FEXT_SCANREQ) { ieee80211_check_scan(vap, vap->iv_scanreq_flags, vap->iv_scanreq_duration, vap->iv_scanreq_mindwell, vap->iv_scanreq_maxdwell, vap->iv_scanreq_nssid, vap->iv_scanreq_ssid); vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANREQ; } else ieee80211_check_scan_current(vap); break; case IEEE80211_S_SCAN: /* * A state change requires a reset; scan. */ ieee80211_check_scan_current(vap); break; default: break; } break; case IEEE80211_S_CAC: /* * Start CAC on a DFS channel. We come here when starting * a bss on a DFS channel (see ieee80211_create_ibss). */ ieee80211_dfs_cac_start(vap); break; case IEEE80211_S_RUN: if (vap->iv_flags & IEEE80211_F_WPA) { /* XXX validate prerequisites */ } switch (ostate) { case IEEE80211_S_INIT: /* * Already have a channel; bypass the * scan and startup immediately. * Note that ieee80211_create_ibss will call * back to do a RUN->RUN state change. */ ieee80211_create_ibss(vap, ieee80211_ht_adjust_channel(ic, ic->ic_curchan, vap->iv_flags_ht)); /* NB: iv_bss is changed on return */ break; case IEEE80211_S_CAC: /* * NB: This is the normal state change when CAC * expires and no radar was detected; no need to * clear the CAC timer as it's already expired. */ /* fall thru... */ case IEEE80211_S_CSA: /* * Shorten inactivity timer of associated stations * to weed out sta's that don't follow a CSA. */ ieee80211_iterate_nodes(&ic->ic_sta, sta_csa, vap); /* * Update bss node channel to reflect where * we landed after CSA. */ ieee80211_node_set_chan(vap->iv_bss, ieee80211_ht_adjust_channel(ic, ic->ic_curchan, ieee80211_htchanflags(vap->iv_bss->ni_chan))); /* XXX bypass debug msgs */ break; case IEEE80211_S_SCAN: case IEEE80211_S_RUN: #ifdef IEEE80211_DEBUG if (ieee80211_msg_debug(vap)) { struct ieee80211_node *ni = vap->iv_bss; ieee80211_note(vap, "synchronized with %s ssid ", ether_sprintf(ni->ni_bssid)); ieee80211_print_essid(ni->ni_essid, ni->ni_esslen); /* XXX MCS/HT */ printf(" channel %d start %uMb\n", ieee80211_chan2ieee(ic, ic->ic_curchan), IEEE80211_RATE2MBS(ni->ni_txrate)); } #endif break; default: break; } /* * Start/stop the authenticator. We delay until here * to allow configuration to happen out of order. */ if (vap->iv_auth->ia_attach != NULL) { /* XXX check failure */ vap->iv_auth->ia_attach(vap); } else if (vap->iv_auth->ia_detach != NULL) { vap->iv_auth->ia_detach(vap); } ieee80211_node_authorize(vap->iv_bss); break; case IEEE80211_S_CSA: if (ostate == IEEE80211_S_RUN && isbandchange(ic)) { /* * On a ``band change'' silently drop associated * stations as they must re-associate before they * can pass traffic (as otherwise protocol state * such as capabilities and the negotiated rate * set may/will be wrong). */ ieee80211_iterate_nodes(&ic->ic_sta, sta_drop, vap); } break; default: break; } return 0; } static void hostap_deliver_data(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m) { struct ether_header *eh = mtod(m, struct ether_header *); struct ifnet *ifp = vap->iv_ifp; /* clear driver/net80211 flags before passing up */ m->m_flags &= ~(M_MCAST | M_BCAST); m_clrprotoflags(m); KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP, ("gack, opmode %d", vap->iv_opmode)); /* * Do accounting. */ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); IEEE80211_NODE_STAT(ni, rx_data); IEEE80211_NODE_STAT_ADD(ni, rx_bytes, m->m_pkthdr.len); if (ETHER_IS_MULTICAST(eh->ether_dhost)) { m->m_flags |= M_MCAST; /* XXX M_BCAST? */ IEEE80211_NODE_STAT(ni, rx_mcast); } else IEEE80211_NODE_STAT(ni, rx_ucast); /* perform as a bridge within the AP */ if ((vap->iv_flags & IEEE80211_F_NOBRIDGE) == 0) { struct mbuf *mcopy = NULL; if (m->m_flags & M_MCAST) { mcopy = m_dup(m, M_NOWAIT); if (mcopy == NULL) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); else mcopy->m_flags |= M_MCAST; } else { /* * Check if the destination is associated with the * same vap and authorized to receive traffic. * Beware of traffic destined for the vap itself; * sending it will not work; just let it be delivered * normally. */ struct ieee80211_node *sta = ieee80211_find_vap_node( &vap->iv_ic->ic_sta, vap, eh->ether_dhost); if (sta != NULL) { if (ieee80211_node_is_authorized(sta)) { /* * Beware of sending to ourself; this * needs to happen via the normal * input path. */ if (sta != vap->iv_bss) { mcopy = m; m = NULL; } } else { vap->iv_stats.is_rx_unauth++; IEEE80211_NODE_STAT(sta, rx_unauth); } ieee80211_free_node(sta); } } if (mcopy != NULL) { int len, err; len = mcopy->m_pkthdr.len; err = ieee80211_vap_xmitpkt(vap, mcopy); if (err) { /* NB: IFQ_HANDOFF reclaims mcopy */ } else { if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); } } } if (m != NULL) { /* * Mark frame as coming from vap's interface. */ m->m_pkthdr.rcvif = ifp; if (m->m_flags & M_MCAST) { /* * Spam DWDS vap's w/ multicast traffic. */ /* XXX only if dwds in use? */ ieee80211_dwds_mcast(vap, m); } if (ni->ni_vlan != 0) { /* attach vlan tag */ m->m_pkthdr.ether_vtag = ni->ni_vlan; m->m_flags |= M_VLANTAG; } ifp->if_input(ifp, m); } } /* * Decide if a received management frame should be * printed when debugging is enabled. This filters some * of the less interesting frames that come frequently * (e.g. beacons). */ static __inline int doprint(struct ieee80211vap *vap, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_BEACON: return (vap->iv_ic->ic_flags & IEEE80211_F_SCAN); case IEEE80211_FC0_SUBTYPE_PROBE_REQ: return 0; } return 1; } /* * Process a received frame. The node associated with the sender * should be supplied. If nothing was found in the node table then * the caller is assumed to supply a reference to iv_bss instead. * The RSSI and a timestamp are also supplied. The RSSI data is used * during AP scanning to select a AP to associate with; it can have * any units so long as values have consistent units and higher values * mean ``better signal''. The receive timestamp is currently not used * by the 802.11 layer. */ static int hostap_input(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = vap->iv_ifp; struct ieee80211_frame *wh; struct ieee80211_key *key; struct ether_header *eh; int hdrspace, need_tap = 1; /* mbuf need to be tapped. */ uint8_t dir, type, subtype, qos; uint8_t *bssid; if (m->m_flags & M_AMPDU_MPDU) { /* * Fastpath for A-MPDU reorder q resubmission. Frames * w/ M_AMPDU_MPDU marked have already passed through * here but were received out of order and been held on * the reorder queue. When resubmitted they are marked * with the M_AMPDU_MPDU flag and we can bypass most of * the normal processing. */ wh = mtod(m, struct ieee80211_frame *); type = IEEE80211_FC0_TYPE_DATA; dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; subtype = IEEE80211_FC0_SUBTYPE_QOS; hdrspace = ieee80211_hdrspace(ic, wh); /* XXX optimize? */ goto resubmit_ampdu; } KASSERT(ni != NULL, ("null node")); ni->ni_inact = ni->ni_inact_reload; type = -1; /* undefined */ if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_min)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "too short (1): len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } /* * Bit of a cheat here, we use a pointer for a 3-address * frame format but don't reference fields past outside * ieee80211_frame_min w/o first validating the data is * present. */ wh = mtod(m, struct ieee80211_frame *); if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) != IEEE80211_FC0_VERSION_0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "wrong version, fc %02x:%02x", wh->i_fc[0], wh->i_fc[1]); vap->iv_stats.is_rx_badversion++; goto err; } dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { if (dir != IEEE80211_FC1_DIR_NODS) bssid = wh->i_addr1; else if (type == IEEE80211_FC0_TYPE_CTL) bssid = wh->i_addr1; else { if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "too short (2): len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } bssid = wh->i_addr3; } /* * Validate the bssid. */ if (!(type == IEEE80211_FC0_TYPE_MGT && subtype == IEEE80211_FC0_SUBTYPE_BEACON) && !IEEE80211_ADDR_EQ(bssid, vap->iv_bss->ni_bssid) && !IEEE80211_ADDR_EQ(bssid, ifp->if_broadcastaddr)) { /* not interested in */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, bssid, NULL, "%s", "not to bss"); vap->iv_stats.is_rx_wrongbss++; goto out; } IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; if (IEEE80211_HAS_SEQ(type, subtype)) { uint8_t tid = ieee80211_gettid(wh); if (IEEE80211_QOS_HAS_SEQ(wh) && TID_TO_WME_AC(tid) >= WME_AC_VI) ic->ic_wme.wme_hipri_traffic++; if (! ieee80211_check_rxseq(ni, wh, bssid)) goto out; } } switch (type) { case IEEE80211_FC0_TYPE_DATA: hdrspace = ieee80211_hdrspace(ic, wh); if (m->m_len < hdrspace && (m = m_pullup(m, hdrspace)) == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "data too short: expecting %u", hdrspace); vap->iv_stats.is_rx_tooshort++; goto out; /* XXX */ } if (!(dir == IEEE80211_FC1_DIR_TODS || (dir == IEEE80211_FC1_DIR_DSTODS && (vap->iv_flags & IEEE80211_F_DWDS)))) { if (dir != IEEE80211_FC1_DIR_DSTODS) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "incorrect dir 0x%x", dir); } else { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_WDS, wh, "4-address data", "%s", "DWDS not enabled"); } vap->iv_stats.is_rx_wrongdir++; goto out; } /* check if source STA is associated */ if (ni == vap->iv_bss) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "%s", "unknown src"); ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_NOT_AUTHED); vap->iv_stats.is_rx_notassoc++; goto err; } if (ni->ni_associd == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "%s", "unassoc src"); IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DISASSOC, IEEE80211_REASON_NOT_ASSOCED); vap->iv_stats.is_rx_notassoc++; goto err; } /* * Check for power save state change. * XXX out-of-order A-MPDU frames? */ if (((wh->i_fc[1] & IEEE80211_FC1_PWR_MGT) ^ (ni->ni_flags & IEEE80211_NODE_PWR_MGT))) vap->iv_node_ps(ni, wh->i_fc[1] & IEEE80211_FC1_PWR_MGT); /* * For 4-address packets handle WDS discovery * notifications. Once a WDS link is setup frames * are just delivered to the WDS vap (see below). */ if (dir == IEEE80211_FC1_DIR_DSTODS && ni->ni_wdsvap == NULL) { if (!ieee80211_node_is_authorized(ni)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_WDS, wh, "4-address data", "%s", "unauthorized port"); vap->iv_stats.is_rx_unauth++; IEEE80211_NODE_STAT(ni, rx_unauth); goto err; } ieee80211_dwds_discover(ni, m); return type; } /* * Handle A-MPDU re-ordering. If the frame is to be * processed directly then ieee80211_ampdu_reorder * will return 0; otherwise it has consumed the mbuf * and we should do nothing more with it. */ if ((m->m_flags & M_AMPDU) && ieee80211_ampdu_reorder(ni, m) != 0) { m = NULL; goto out; } resubmit_ampdu: /* * Handle privacy requirements. Note that we * must not be preempted from here until after * we (potentially) call ieee80211_crypto_demic; * otherwise we may violate assumptions in the * crypto cipher modules used to do delayed update * of replay sequence numbers. */ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "WEP", "%s", "PRIVACY off"); vap->iv_stats.is_rx_noprivacy++; IEEE80211_NODE_STAT(ni, rx_noprivacy); goto out; } key = ieee80211_crypto_decap(ni, m, hdrspace); if (key == NULL) { /* NB: stats+msgs handled in crypto_decap */ IEEE80211_NODE_STAT(ni, rx_wepfail); goto out; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; } else { /* XXX M_WEP and IEEE80211_F_PRIVACY */ key = NULL; } /* * Save QoS bits for use below--before we strip the header. */ if (subtype == IEEE80211_FC0_SUBTYPE_QOS) { qos = (dir == IEEE80211_FC1_DIR_DSTODS) ? ((struct ieee80211_qosframe_addr4 *)wh)->i_qos[0] : ((struct ieee80211_qosframe *)wh)->i_qos[0]; } else qos = 0; /* * Next up, any fragmentation. */ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { m = ieee80211_defrag(ni, m, hdrspace); if (m == NULL) { /* Fragment dropped or frame not complete yet */ goto out; } } wh = NULL; /* no longer valid, catch any uses */ /* * Next strip any MSDU crypto bits. */ if (key != NULL && !ieee80211_crypto_demic(vap, key, m, 0)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "%s", "demic error"); vap->iv_stats.is_rx_demicfail++; IEEE80211_NODE_STAT(ni, rx_demicfail); goto out; } /* copy to listener after decrypt */ if (ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); need_tap = 0; /* * Finally, strip the 802.11 header. */ m = ieee80211_decap(vap, m, hdrspace); if (m == NULL) { /* XXX mask bit to check for both */ /* don't count Null data frames as errors */ if (subtype == IEEE80211_FC0_SUBTYPE_NODATA || subtype == IEEE80211_FC0_SUBTYPE_QOS_NULL) goto out; IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "%s", "decap error"); vap->iv_stats.is_rx_decap++; IEEE80211_NODE_STAT(ni, rx_decap); goto err; } eh = mtod(m, struct ether_header *); if (!ieee80211_node_is_authorized(ni)) { /* * Deny any non-PAE frames received prior to * authorization. For open/shared-key * authentication the port is mark authorized * after authentication completes. For 802.1x * the port is not marked authorized by the * authenticator until the handshake has completed. */ if (eh->ether_type != htons(ETHERTYPE_PAE)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, eh->ether_shost, "data", "unauthorized port: ether type 0x%x len %u", eh->ether_type, m->m_pkthdr.len); vap->iv_stats.is_rx_unauth++; IEEE80211_NODE_STAT(ni, rx_unauth); goto err; } } else { /* * When denying unencrypted frames, discard * any non-PAE frames received without encryption. */ if ((vap->iv_flags & IEEE80211_F_DROPUNENC) && (key == NULL && (m->m_flags & M_WEP) == 0) && eh->ether_type != htons(ETHERTYPE_PAE)) { /* * Drop unencrypted frames. */ vap->iv_stats.is_rx_unencrypted++; IEEE80211_NODE_STAT(ni, rx_unencrypted); goto out; } } /* XXX require HT? */ if (qos & IEEE80211_QOS_AMSDU) { m = ieee80211_decap_amsdu(ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; } else { #ifdef IEEE80211_SUPPORT_SUPERG m = ieee80211_decap_fastframe(vap, ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; #endif } if (dir == IEEE80211_FC1_DIR_DSTODS && ni->ni_wdsvap != NULL) ieee80211_deliver_data(ni->ni_wdsvap, ni, m); else hostap_deliver_data(vap, ni, m); return IEEE80211_FC0_TYPE_DATA; case IEEE80211_FC0_TYPE_MGT: vap->iv_stats.is_rx_mgmt++; IEEE80211_NODE_STAT(ni, rx_mgmt); if (dir != IEEE80211_FC1_DIR_NODS) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "mgt", "incorrect dir 0x%x", dir); vap->iv_stats.is_rx_wrongdir++; goto err; } if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "mgt", "too short: len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } if (IEEE80211_IS_MULTICAST(wh->i_addr2)) { /* ensure return frames are unicast */ IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "source is multicast: %s", ether_sprintf(wh->i_addr2)); vap->iv_stats.is_rx_mgtdiscard++; /* XXX stat */ goto out; } #ifdef IEEE80211_DEBUG if ((ieee80211_msg_debug(vap) && doprint(vap, subtype)) || ieee80211_msg_dumppkts(vap)) { if_printf(ifp, "received %s from %s rssi %d\n", ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], ether_sprintf(wh->i_addr2), rssi); } #endif if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { if (subtype != IEEE80211_FC0_SUBTYPE_AUTH) { /* * Only shared key auth frames with a challenge * should be encrypted, discard all others. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "WEP set but not permitted"); vap->iv_stats.is_rx_mgtdiscard++; /* XXX */ goto out; } if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "WEP set but PRIVACY off"); vap->iv_stats.is_rx_noprivacy++; goto out; } hdrspace = ieee80211_hdrspace(ic, wh); key = ieee80211_crypto_decap(ni, m, hdrspace); if (key == NULL) { /* NB: stats+msgs handled in crypto_decap */ goto out; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; } /* * Pass the packet to radiotap before calling iv_recv_mgmt(). * Otherwise iv_recv_mgmt() might pass another packet to * radiotap, resulting in out of order packet captures. */ if (ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); need_tap = 0; vap->iv_recv_mgmt(ni, m, subtype, rxs, rssi, nf); goto out; case IEEE80211_FC0_TYPE_CTL: vap->iv_stats.is_rx_ctl++; IEEE80211_NODE_STAT(ni, rx_ctrl); vap->iv_recv_ctl(ni, m, subtype); goto out; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "bad", "frame type 0x%x", type); /* should not come here */ break; } err: if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); out: if (m != NULL) { if (need_tap && ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); m_freem(m); } return type; } static void hostap_auth_open(struct ieee80211_node *ni, struct ieee80211_frame *wh, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; KASSERT(vap->iv_state == IEEE80211_S_RUN, ("state %d", vap->iv_state)); if (ni->ni_authmode == IEEE80211_AUTH_SHARED) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "open auth", "bad sta auth mode %u", ni->ni_authmode); vap->iv_stats.is_rx_bad_auth++; /* XXX */ /* * Clear any challenge text that may be there if * a previous shared key auth failed and then an * open auth is attempted. */ if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } /* XXX hack to workaround calling convention */ ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, (seq + 1) | (IEEE80211_STATUS_ALG<<16)); return; } if (seq != IEEE80211_AUTH_OPEN_REQUEST) { vap->iv_stats.is_rx_bad_auth++; return; } /* always accept open authentication requests */ if (ni == vap->iv_bss) { ni = ieee80211_dup_bss(vap, wh->i_addr2); if (ni == NULL) return; } else if ((ni->ni_flags & IEEE80211_NODE_AREF) == 0) (void) ieee80211_ref_node(ni); /* * Mark the node as referenced to reflect that it's * reference count has been bumped to insure it remains * after the transaction completes. */ ni->ni_flags |= IEEE80211_NODE_AREF; /* * Mark the node as requiring a valid association id * before outbound traffic is permitted. */ ni->ni_flags |= IEEE80211_NODE_ASSOCID; if (vap->iv_acl != NULL && vap->iv_acl->iac_getpolicy(vap) == IEEE80211_MACCMD_POLICY_RADIUS) { /* * When the ACL policy is set to RADIUS we defer the * authorization to a user agent. Dispatch an event, * a subsequent MLME call will decide the fate of the * station. If the user agent is not present then the * node will be reclaimed due to inactivity. */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH | IEEE80211_MSG_ACL, ni->ni_macaddr, "%s", "station authentication defered (radius acl)"); ieee80211_notify_node_auth(ni); } else { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni->ni_macaddr, "%s", "station authenticated (open)"); /* * When 802.1x is not in use mark the port * authorized at this point so traffic can flow. */ if (ni->ni_authmode != IEEE80211_AUTH_8021X) ieee80211_node_authorize(ni); } } static void hostap_auth_shared(struct ieee80211_node *ni, struct ieee80211_frame *wh, uint8_t *frm, uint8_t *efrm, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; uint8_t *challenge; int allocbs, estatus; KASSERT(vap->iv_state == IEEE80211_S_RUN, ("state %d", vap->iv_state)); /* * NB: this can happen as we allow pre-shared key * authentication to be enabled w/o wep being turned * on so that configuration of these can be done * in any order. It may be better to enforce the * ordering in which case this check would just be * for sanity/consistency. */ if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "%s", " PRIVACY is disabled"); estatus = IEEE80211_STATUS_ALG; goto bad; } /* * Pre-shared key authentication is evil; accept * it only if explicitly configured (it is supported * mainly for compatibility with clients like Mac OS X). */ if (ni->ni_authmode != IEEE80211_AUTH_AUTO && ni->ni_authmode != IEEE80211_AUTH_SHARED) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad sta auth mode %u", ni->ni_authmode); vap->iv_stats.is_rx_bad_auth++; /* XXX maybe a unique error? */ estatus = IEEE80211_STATUS_ALG; goto bad; } challenge = NULL; if (frm + 1 < efrm) { if ((frm[1] + 2) > (efrm - frm)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "ie %d/%d too long", frm[0], (frm[1] + 2) - (efrm - frm)); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (*frm == IEEE80211_ELEMID_CHALLENGE) challenge = frm; frm += frm[1] + 2; } switch (seq) { case IEEE80211_AUTH_SHARED_CHALLENGE: case IEEE80211_AUTH_SHARED_RESPONSE: if (challenge == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "%s", "no challenge"); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (challenge[1] != IEEE80211_CHALLENGE_LEN) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad challenge len %d", challenge[1]); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } default: break; } switch (seq) { case IEEE80211_AUTH_SHARED_REQUEST: if (ni == vap->iv_bss) { ni = ieee80211_dup_bss(vap, wh->i_addr2); if (ni == NULL) { /* NB: no way to return an error */ return; } allocbs = 1; } else { if ((ni->ni_flags & IEEE80211_NODE_AREF) == 0) (void) ieee80211_ref_node(ni); allocbs = 0; } /* * Mark the node as referenced to reflect that it's * reference count has been bumped to insure it remains * after the transaction completes. */ ni->ni_flags |= IEEE80211_NODE_AREF; /* * Mark the node as requiring a valid associatio id * before outbound traffic is permitted. */ ni->ni_flags |= IEEE80211_NODE_ASSOCID; IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; if (!ieee80211_alloc_challenge(ni)) { /* NB: don't return error so they rexmit */ return; } get_random_bytes(ni->ni_challenge, IEEE80211_CHALLENGE_LEN); IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, "shared key %sauth request", allocbs ? "" : "re"); /* * When the ACL policy is set to RADIUS we defer the * authorization to a user agent. Dispatch an event, * a subsequent MLME call will decide the fate of the * station. If the user agent is not present then the * node will be reclaimed due to inactivity. */ if (vap->iv_acl != NULL && vap->iv_acl->iac_getpolicy(vap) == IEEE80211_MACCMD_POLICY_RADIUS) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH | IEEE80211_MSG_ACL, ni->ni_macaddr, "%s", "station authentication defered (radius acl)"); ieee80211_notify_node_auth(ni); return; } break; case IEEE80211_AUTH_SHARED_RESPONSE: if (ni == vap->iv_bss) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key response", "%s", "unknown station"); /* NB: don't send a response */ return; } if (ni->ni_challenge == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key response", "%s", "no challenge recorded"); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (memcmp(ni->ni_challenge, &challenge[2], challenge[1]) != 0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key response", "%s", "challenge mismatch"); vap->iv_stats.is_rx_auth_fail++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, "%s", "station authenticated (shared key)"); ieee80211_node_authorize(ni); break; default: IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad seq %d", seq); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_SEQUENCE; goto bad; } IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); return; bad: /* * Send an error response; but only when operating as an AP. */ /* XXX hack to workaround calling convention */ ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, (seq + 1) | (estatus<<16)); } /* * Convert a WPA cipher selector OUI to an internal * cipher algorithm. Where appropriate we also * record any key length. */ static int wpa_cipher(const uint8_t *sel, uint8_t *keylen) { #define WPA_SEL(x) (((x)<<24)|WPA_OUI) uint32_t w = LE_READ_4(sel); switch (w) { case WPA_SEL(WPA_CSE_NULL): return IEEE80211_CIPHER_NONE; case WPA_SEL(WPA_CSE_WEP40): if (keylen) *keylen = 40 / NBBY; return IEEE80211_CIPHER_WEP; case WPA_SEL(WPA_CSE_WEP104): if (keylen) *keylen = 104 / NBBY; return IEEE80211_CIPHER_WEP; case WPA_SEL(WPA_CSE_TKIP): return IEEE80211_CIPHER_TKIP; case WPA_SEL(WPA_CSE_CCMP): return IEEE80211_CIPHER_AES_CCM; } return 32; /* NB: so 1<< is discarded */ #undef WPA_SEL } /* * Convert a WPA key management/authentication algorithm * to an internal code. */ static int wpa_keymgmt(const uint8_t *sel) { #define WPA_SEL(x) (((x)<<24)|WPA_OUI) uint32_t w = LE_READ_4(sel); switch (w) { case WPA_SEL(WPA_ASE_8021X_UNSPEC): return WPA_ASE_8021X_UNSPEC; case WPA_SEL(WPA_ASE_8021X_PSK): return WPA_ASE_8021X_PSK; case WPA_SEL(WPA_ASE_NONE): return WPA_ASE_NONE; } return 0; /* NB: so is discarded */ #undef WPA_SEL } /* * Parse a WPA information element to collect parameters. * Note that we do not validate security parameters; that * is handled by the authenticator; the parsing done here * is just for internal use in making operational decisions. */ static int ieee80211_parse_wpa(struct ieee80211vap *vap, const uint8_t *frm, struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh) { uint8_t len = frm[1]; uint32_t w; int n; /* * Check the length once for fixed parts: OUI, type, * version, mcast cipher, and 2 selector counts. * Other, variable-length data, must be checked separately. */ if ((vap->iv_flags & IEEE80211_F_WPA1) == 0) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "not WPA, flags 0x%x", vap->iv_flags); return IEEE80211_REASON_IE_INVALID; } if (len < 14) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "too short, len %u", len); return IEEE80211_REASON_IE_INVALID; } frm += 6, len -= 4; /* NB: len is payload only */ /* NB: iswpaoui already validated the OUI and type */ w = LE_READ_2(frm); if (w != WPA_VERSION) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "bad version %u", w); return IEEE80211_REASON_IE_INVALID; } frm += 2, len -= 2; memset(rsn, 0, sizeof(*rsn)); /* multicast/group cipher */ rsn->rsn_mcastcipher = wpa_cipher(frm, &rsn->rsn_mcastkeylen); frm += 4, len -= 4; /* unicast ciphers */ n = LE_READ_2(frm); frm += 2, len -= 2; if (len < n*4+2) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "ucast cipher data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { w |= 1<rsn_ucastkeylen); frm += 4, len -= 4; } if (w & (1<rsn_ucastcipher = IEEE80211_CIPHER_TKIP; else rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; /* key management algorithms */ n = LE_READ_2(frm); frm += 2, len -= 2; if (len < n*4) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "key mgmt alg data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { w |= wpa_keymgmt(frm); frm += 4, len -= 4; } if (w & WPA_ASE_8021X_UNSPEC) rsn->rsn_keymgmt = WPA_ASE_8021X_UNSPEC; else rsn->rsn_keymgmt = WPA_ASE_8021X_PSK; if (len > 2) /* optional capabilities */ rsn->rsn_caps = LE_READ_2(frm); return 0; } /* * Convert an RSN cipher selector OUI to an internal * cipher algorithm. Where appropriate we also * record any key length. */ static int rsn_cipher(const uint8_t *sel, uint8_t *keylen) { #define RSN_SEL(x) (((x)<<24)|RSN_OUI) uint32_t w = LE_READ_4(sel); switch (w) { case RSN_SEL(RSN_CSE_NULL): return IEEE80211_CIPHER_NONE; case RSN_SEL(RSN_CSE_WEP40): if (keylen) *keylen = 40 / NBBY; return IEEE80211_CIPHER_WEP; case RSN_SEL(RSN_CSE_WEP104): if (keylen) *keylen = 104 / NBBY; return IEEE80211_CIPHER_WEP; case RSN_SEL(RSN_CSE_TKIP): return IEEE80211_CIPHER_TKIP; case RSN_SEL(RSN_CSE_CCMP): return IEEE80211_CIPHER_AES_CCM; case RSN_SEL(RSN_CSE_WRAP): return IEEE80211_CIPHER_AES_OCB; } return 32; /* NB: so 1<< is discarded */ #undef WPA_SEL } /* * Convert an RSN key management/authentication algorithm * to an internal code. */ static int rsn_keymgmt(const uint8_t *sel) { #define RSN_SEL(x) (((x)<<24)|RSN_OUI) uint32_t w = LE_READ_4(sel); switch (w) { case RSN_SEL(RSN_ASE_8021X_UNSPEC): return RSN_ASE_8021X_UNSPEC; case RSN_SEL(RSN_ASE_8021X_PSK): return RSN_ASE_8021X_PSK; case RSN_SEL(RSN_ASE_NONE): return RSN_ASE_NONE; } return 0; /* NB: so is discarded */ #undef RSN_SEL } /* * Parse a WPA/RSN information element to collect parameters * and validate the parameters against what has been * configured for the system. */ static int ieee80211_parse_rsn(struct ieee80211vap *vap, const uint8_t *frm, struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh) { uint8_t len = frm[1]; uint32_t w; int n; /* * Check the length once for fixed parts: * version, mcast cipher, and 2 selector counts. * Other, variable-length data, must be checked separately. */ if ((vap->iv_flags & IEEE80211_F_WPA2) == 0) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "not RSN, flags 0x%x", vap->iv_flags); return IEEE80211_REASON_IE_INVALID; } if (len < 10) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "too short, len %u", len); return IEEE80211_REASON_IE_INVALID; } frm += 2; w = LE_READ_2(frm); if (w != RSN_VERSION) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "bad version %u", w); return IEEE80211_REASON_IE_INVALID; } frm += 2, len -= 2; memset(rsn, 0, sizeof(*rsn)); /* multicast/group cipher */ rsn->rsn_mcastcipher = rsn_cipher(frm, &rsn->rsn_mcastkeylen); frm += 4, len -= 4; /* unicast ciphers */ n = LE_READ_2(frm); frm += 2, len -= 2; if (len < n*4+2) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "ucast cipher data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { w |= 1<rsn_ucastkeylen); frm += 4, len -= 4; } if (w & (1<rsn_ucastcipher = IEEE80211_CIPHER_TKIP; else rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; /* key management algorithms */ n = LE_READ_2(frm); frm += 2, len -= 2; if (len < n*4) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "key mgmt alg data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { w |= rsn_keymgmt(frm); frm += 4, len -= 4; } if (w & RSN_ASE_8021X_UNSPEC) rsn->rsn_keymgmt = RSN_ASE_8021X_UNSPEC; else rsn->rsn_keymgmt = RSN_ASE_8021X_PSK; /* optional RSN capabilities */ if (len > 2) rsn->rsn_caps = LE_READ_2(frm); /* XXXPMKID */ return 0; } /* * WPA/802.11i assocation request processing. */ static int wpa_assocreq(struct ieee80211_node *ni, struct ieee80211_rsnparms *rsnparms, const struct ieee80211_frame *wh, const uint8_t *wpa, const uint8_t *rsn, uint16_t capinfo) { struct ieee80211vap *vap = ni->ni_vap; uint8_t reason; int badwparsn; ni->ni_flags &= ~(IEEE80211_NODE_WPS|IEEE80211_NODE_TSN); if (wpa == NULL && rsn == NULL) { if (vap->iv_flags_ext & IEEE80211_FEXT_WPS) { /* * W-Fi Protected Setup (WPS) permits * clients to associate and pass EAPOL frames * to establish initial credentials. */ ni->ni_flags |= IEEE80211_NODE_WPS; return 1; } if ((vap->iv_flags_ext & IEEE80211_FEXT_TSN) && (capinfo & IEEE80211_CAPINFO_PRIVACY)) { /* * Transitional Security Network. Permits clients * to associate and use WEP while WPA is configured. */ ni->ni_flags |= IEEE80211_NODE_TSN; return 1; } IEEE80211_DISCARD(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, wh, NULL, "%s", "no WPA/RSN IE in association request"); vap->iv_stats.is_rx_assoc_badwpaie++; reason = IEEE80211_REASON_IE_INVALID; goto bad; } /* assert right association security credentials */ badwparsn = 0; /* NB: to silence compiler */ switch (vap->iv_flags & IEEE80211_F_WPA) { case IEEE80211_F_WPA1: badwparsn = (wpa == NULL); break; case IEEE80211_F_WPA2: badwparsn = (rsn == NULL); break; case IEEE80211_F_WPA1|IEEE80211_F_WPA2: badwparsn = (wpa == NULL && rsn == NULL); break; } if (badwparsn) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, wh, NULL, "%s", "missing WPA/RSN IE in association request"); vap->iv_stats.is_rx_assoc_badwpaie++; reason = IEEE80211_REASON_IE_INVALID; goto bad; } /* * Parse WPA/RSN information element. */ if (wpa != NULL) reason = ieee80211_parse_wpa(vap, wpa, rsnparms, wh); else reason = ieee80211_parse_rsn(vap, rsn, rsnparms, wh); if (reason != 0) { /* XXX distinguish WPA/RSN? */ vap->iv_stats.is_rx_assoc_badwpaie++; goto bad; } IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, ni, "%s ie: mc %u/%u uc %u/%u key %u caps 0x%x", wpa != NULL ? "WPA" : "RSN", rsnparms->rsn_mcastcipher, rsnparms->rsn_mcastkeylen, rsnparms->rsn_ucastcipher, rsnparms->rsn_ucastkeylen, rsnparms->rsn_keymgmt, rsnparms->rsn_caps); return 1; bad: ieee80211_node_deauth(ni, reason); return 0; } /* XXX find a better place for definition */ struct l2_update_frame { struct ether_header eh; uint8_t dsap; uint8_t ssap; uint8_t control; uint8_t xid[3]; } __packed; /* * Deliver a TGf L2UF frame on behalf of a station. * This primes any bridge when the station is roaming * between ap's on the same wired network. */ static void ieee80211_deliver_l2uf(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ifnet *ifp = vap->iv_ifp; struct mbuf *m; struct l2_update_frame *l2uf; struct ether_header *eh; m = m_gethdr(M_NOWAIT, MT_DATA); if (m == NULL) { IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, "%s", "no mbuf for l2uf frame"); vap->iv_stats.is_rx_nobuf++; /* XXX not right */ return; } l2uf = mtod(m, struct l2_update_frame *); eh = &l2uf->eh; /* dst: Broadcast address */ IEEE80211_ADDR_COPY(eh->ether_dhost, ifp->if_broadcastaddr); /* src: associated STA */ IEEE80211_ADDR_COPY(eh->ether_shost, ni->ni_macaddr); eh->ether_type = htons(sizeof(*l2uf) - sizeof(*eh)); l2uf->dsap = 0; l2uf->ssap = 0; l2uf->control = 0xf5; l2uf->xid[0] = 0x81; l2uf->xid[1] = 0x80; l2uf->xid[2] = 0x00; m->m_pkthdr.len = m->m_len = sizeof(*l2uf); hostap_deliver_data(vap, ni, m); } static void ratesetmismatch(struct ieee80211_node *ni, const struct ieee80211_frame *wh, int reassoc, int resp, const char *tag, int rate) { IEEE80211_NOTE_MAC(ni->ni_vap, IEEE80211_MSG_ANY, wh->i_addr2, "deny %s request, %s rate set mismatch, rate/MCS %d", reassoc ? "reassoc" : "assoc", tag, rate & IEEE80211_RATE_VAL); IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_BASIC_RATE); ieee80211_node_leave(ni); } static void capinfomismatch(struct ieee80211_node *ni, const struct ieee80211_frame *wh, int reassoc, int resp, const char *tag, int capinfo) { struct ieee80211vap *vap = ni->ni_vap; IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ANY, wh->i_addr2, "deny %s request, %s mismatch 0x%x", reassoc ? "reassoc" : "assoc", tag, capinfo); IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_CAPINFO); ieee80211_node_leave(ni); vap->iv_stats.is_rx_assoc_capmismatch++; } static void htcapmismatch(struct ieee80211_node *ni, const struct ieee80211_frame *wh, int reassoc, int resp) { IEEE80211_NOTE_MAC(ni->ni_vap, IEEE80211_MSG_ANY, wh->i_addr2, "deny %s request, %s missing HT ie", reassoc ? "reassoc" : "assoc"); /* XXX no better code */ IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_MISSING_HT_CAPS); ieee80211_node_leave(ni); } static void authalgreject(struct ieee80211_node *ni, const struct ieee80211_frame *wh, int algo, int seq, int status) { struct ieee80211vap *vap = ni->ni_vap; IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "unsupported alg %d", algo); vap->iv_stats.is_rx_auth_unsupported++; ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, seq | (status << 16)); } static __inline int ishtmixed(const uint8_t *ie) { const struct ieee80211_ie_htinfo *ht = (const struct ieee80211_ie_htinfo *) ie; return (ht->hi_byte2 & IEEE80211_HTINFO_OPMODE) == IEEE80211_HTINFO_OPMODE_MIXED; } static int is11bclient(const uint8_t *rates, const uint8_t *xrates) { static const uint32_t brates = (1<<2*1)|(1<<2*2)|(1<<11)|(1<<2*11); int i; /* NB: the 11b clients we care about will not have xrates */ if (xrates != NULL || rates == NULL) return 0; for (i = 0; i < rates[1]; i++) { int r = rates[2+i] & IEEE80211_RATE_VAL; if (r > 2*11 || ((1<ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame *wh; uint8_t *frm, *efrm, *sfrm; uint8_t *ssid, *rates, *xrates, *wpa, *rsn, *wme, *ath, *htcap; int reassoc, resp; uint8_t rate; wh = mtod(m0, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; efrm = mtod(m0, uint8_t *) + m0->m_len; switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: case IEEE80211_FC0_SUBTYPE_BEACON: { struct ieee80211_scanparams scan; /* * We process beacon/probe response frames when scanning; * otherwise we check beacon frames for overlapping non-ERP * BSS in 11g and/or overlapping legacy BSS when in HT. */ if ((ic->ic_flags & IEEE80211_F_SCAN) == 0 && subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* NB: accept off-channel frames */ /* XXX TODO: use rxstatus to determine off-channel details */ if (ieee80211_parse_beacon(ni, m0, ic->ic_curchan, &scan) &~ IEEE80211_BPARSE_OFFCHAN) return; /* * Count frame now that we know it's to be processed. */ if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { vap->iv_stats.is_rx_beacon++; /* XXX remove */ IEEE80211_NODE_STAT(ni, rx_beacons); } else IEEE80211_NODE_STAT(ni, rx_proberesp); /* * If scanning, just pass information to the scan module. */ if (ic->ic_flags & IEEE80211_F_SCAN) { if (scan.status == 0 && /* NB: on channel */ (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN)) { /* * Actively scanning a channel marked passive; * send a probe request now that we know there * is 802.11 traffic present. * * XXX check if the beacon we recv'd gives * us what we need and suppress the probe req */ ieee80211_probe_curchan(vap, 1); ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN; } ieee80211_add_scan(vap, ic->ic_curchan, &scan, wh, subtype, rssi, nf); return; } /* * Check beacon for overlapping bss w/ non ERP stations. * If we detect one and protection is configured but not * enabled, enable it and start a timer that'll bring us * out if we stop seeing the bss. */ if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && scan.status == 0 && /* NB: on-channel */ ((scan.erp & 0x100) == 0 || /* NB: no ERP, 11b sta*/ (scan.erp & IEEE80211_ERP_NON_ERP_PRESENT))) { ic->ic_lastnonerp = ticks; ic->ic_flags_ext |= IEEE80211_FEXT_NONERP_PR; if (ic->ic_protmode != IEEE80211_PROT_NONE && (ic->ic_flags & IEEE80211_F_USEPROT) == 0) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_ASSOC, wh, "non-ERP present on channel %d " "(saw erp 0x%x from channel %d), " "enable use of protection", ic->ic_curchan->ic_ieee, scan.erp, scan.chan); ic->ic_flags |= IEEE80211_F_USEPROT; ieee80211_notify_erp(ic); } } /* * Check beacon for non-HT station on HT channel * and update HT BSS occupancy as appropriate. */ if (IEEE80211_IS_CHAN_HT(ic->ic_curchan)) { if (scan.status & IEEE80211_BPARSE_OFFCHAN) { /* * Off control channel; only check frames * that come in the extension channel when * operating w/ HT40. */ if (!IEEE80211_IS_CHAN_HT40(ic->ic_curchan)) break; if (scan.chan != ic->ic_curchan->ic_extieee) break; } if (scan.htinfo == NULL) { ieee80211_htprot_update(ic, IEEE80211_HTINFO_OPMODE_PROTOPT | IEEE80211_HTINFO_NONHT_PRESENT); } else if (ishtmixed(scan.htinfo)) { /* XXX? take NONHT_PRESENT from beacon? */ ieee80211_htprot_update(ic, IEEE80211_HTINFO_OPMODE_MIXED | IEEE80211_HTINFO_NONHT_PRESENT); } } break; } case IEEE80211_FC0_SUBTYPE_PROBE_REQ: if (vap->iv_state != IEEE80211_S_RUN) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* * Consult the ACL policy module if setup. */ if (vap->iv_acl != NULL && !vap->iv_acl->iac_check(vap, wh)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACL, wh, NULL, "%s", "disallowed by ACL"); vap->iv_stats.is_rx_acl++; return; } /* * prreq frame format * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates */ ssid = rates = xrates = NULL; sfrm = frm; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); switch (*frm) { case IEEE80211_ELEMID_SSID: ssid = frm; break; case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); if (xrates != NULL) IEEE80211_VERIFY_ELEMENT(xrates, IEEE80211_RATE_MAXSIZE - rates[1], return); IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN, return); IEEE80211_VERIFY_SSID(vap->iv_bss, ssid, return); if ((vap->iv_flags & IEEE80211_F_HIDESSID) && ssid[1] == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "no ssid with ssid suppression enabled"); vap->iv_stats.is_rx_ssidmismatch++; /*XXX*/ return; } /* XXX find a better class or define it's own */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2, "%s", "recv probe req"); /* * Some legacy 11b clients cannot hack a complete * probe response frame. When the request includes * only a bare-bones rate set, communicate this to * the transmit side. */ ieee80211_send_proberesp(vap, wh->i_addr2, is11bclient(rates, xrates) ? IEEE80211_SEND_LEGACY_11B : 0); break; case IEEE80211_FC0_SUBTYPE_AUTH: { uint16_t algo, seq, status; if (vap->iv_state != IEEE80211_S_RUN) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_bss->ni_bssid)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "%s", "wrong bssid"); vap->iv_stats.is_rx_wrongbss++; /*XXX unique stat?*/ return; } /* * auth frame format * [2] algorithm * [2] sequence * [2] status * [tlv*] challenge */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); algo = le16toh(*(uint16_t *)frm); seq = le16toh(*(uint16_t *)(frm + 2)); status = le16toh(*(uint16_t *)(frm + 4)); IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr2, "recv auth frame with algorithm %d seq %d", algo, seq); /* * Consult the ACL policy module if setup. */ if (vap->iv_acl != NULL && !vap->iv_acl->iac_check(vap, wh)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACL, wh, NULL, "%s", "disallowed by ACL"); vap->iv_stats.is_rx_acl++; ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, (seq+1) | (IEEE80211_STATUS_UNSPECIFIED<<16)); return; } if (vap->iv_flags & IEEE80211_F_COUNTERM) { IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH | IEEE80211_MSG_CRYPTO, wh, NULL, "%s", "TKIP countermeasures enabled"); vap->iv_stats.is_rx_auth_countermeasures++; ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, IEEE80211_REASON_MIC_FAILURE); return; } if (algo == IEEE80211_AUTH_ALG_SHARED) hostap_auth_shared(ni, wh, frm + 6, efrm, rssi, nf, seq, status); else if (algo == IEEE80211_AUTH_ALG_OPEN) hostap_auth_open(ni, wh, rssi, nf, seq, status); else if (algo == IEEE80211_AUTH_ALG_LEAP) { authalgreject(ni, wh, algo, seq+1, IEEE80211_STATUS_ALG); return; } else { /* * We assume that an unknown algorithm is the result * of a decryption failure on a shared key auth frame; * return a status code appropriate for that instead * of IEEE80211_STATUS_ALG. * * NB: a seq# of 4 is intentional; the decrypted * frame likely has a bogus seq value. */ authalgreject(ni, wh, algo, 4, IEEE80211_STATUS_CHALLENGE); return; } break; } case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: { uint16_t capinfo, lintval; struct ieee80211_rsnparms rsnparms; if (vap->iv_state != IEEE80211_S_RUN) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_bss->ni_bssid)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "%s", "wrong bssid"); vap->iv_stats.is_rx_assoc_bss++; return; } if (subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) { reassoc = 1; resp = IEEE80211_FC0_SUBTYPE_REASSOC_RESP; } else { reassoc = 0; resp = IEEE80211_FC0_SUBTYPE_ASSOC_RESP; } if (ni == vap->iv_bss) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ANY, wh->i_addr2, "deny %s request, sta not authenticated", reassoc ? "reassoc" : "assoc"); ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_ASSOC_NOT_AUTHED); vap->iv_stats.is_rx_assoc_notauth++; return; } /* * asreq frame format * [2] capability information * [2] listen interval * [6*] current AP address (reassoc only) * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates * [tlv] WPA or RSN * [tlv] HT capabilities * [tlv] Atheros capabilities */ IEEE80211_VERIFY_LENGTH(efrm - frm, (reassoc ? 10 : 4), return); capinfo = le16toh(*(uint16_t *)frm); frm += 2; lintval = le16toh(*(uint16_t *)frm); frm += 2; if (reassoc) frm += 6; /* ignore current AP info */ ssid = rates = xrates = wpa = rsn = wme = ath = htcap = NULL; sfrm = frm; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); switch (*frm) { case IEEE80211_ELEMID_SSID: ssid = frm; break; case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; case IEEE80211_ELEMID_RSN: rsn = frm; break; case IEEE80211_ELEMID_HTCAP: htcap = frm; break; case IEEE80211_ELEMID_VENDOR: if (iswpaoui(frm)) wpa = frm; else if (iswmeinfo(frm)) wme = frm; #ifdef IEEE80211_SUPPORT_SUPERG else if (isatherosoui(frm)) ath = frm; #endif else if (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) { if (ishtcapoui(frm) && htcap == NULL) htcap = frm; } break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); if (xrates != NULL) IEEE80211_VERIFY_ELEMENT(xrates, IEEE80211_RATE_MAXSIZE - rates[1], return); IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN, return); IEEE80211_VERIFY_SSID(vap->iv_bss, ssid, return); if (htcap != NULL) { IEEE80211_VERIFY_LENGTH(htcap[1], htcap[0] == IEEE80211_ELEMID_VENDOR ? 4 + sizeof(struct ieee80211_ie_htcap)-2 : sizeof(struct ieee80211_ie_htcap)-2, return); /* XXX just NULL out? */ } if ((vap->iv_flags & IEEE80211_F_WPA) && !wpa_assocreq(ni, &rsnparms, wh, wpa, rsn, capinfo)) return; /* discard challenge after association */ if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } /* NB: 802.11 spec says to ignore station's privacy bit */ if ((capinfo & IEEE80211_CAPINFO_ESS) == 0) { capinfomismatch(ni, wh, reassoc, resp, "capability", capinfo); return; } /* * Disallow re-associate w/ invalid slot time setting. */ if (ni->ni_associd != 0 && IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && ((ni->ni_capinfo ^ capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME)) { capinfomismatch(ni, wh, reassoc, resp, "slot time", capinfo); return; } rate = ieee80211_setup_rates(ni, rates, xrates, IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); if (rate & IEEE80211_RATE_BASIC) { ratesetmismatch(ni, wh, reassoc, resp, "legacy", rate); vap->iv_stats.is_rx_assoc_norate++; return; } /* * If constrained to 11g-only stations reject an * 11b-only station. We cheat a bit here by looking * at the max negotiated xmit rate and assuming anyone * with a best rate <24Mb/s is an 11b station. */ if ((vap->iv_flags & IEEE80211_F_PUREG) && rate < 48) { ratesetmismatch(ni, wh, reassoc, resp, "11g", rate); vap->iv_stats.is_rx_assoc_norate++; return; } /* * Do HT rate set handling and setup HT node state. */ ni->ni_chan = vap->iv_bss->ni_chan; if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && htcap != NULL) { rate = ieee80211_setup_htrates(ni, htcap, IEEE80211_F_DOFMCS | IEEE80211_F_DONEGO | IEEE80211_F_DOBRS); if (rate & IEEE80211_RATE_BASIC) { ratesetmismatch(ni, wh, reassoc, resp, "HT", rate); vap->iv_stats.is_ht_assoc_norate++; return; } ieee80211_ht_node_init(ni); ieee80211_ht_updatehtcap(ni, htcap); } else if (ni->ni_flags & IEEE80211_NODE_HT) ieee80211_ht_node_cleanup(ni); #ifdef IEEE80211_SUPPORT_SUPERG - else if (ni->ni_ath_flags & IEEE80211_NODE_ATH) - ieee80211_ff_node_cleanup(ni); + /* Always do ff node cleanup; for A-MSDU */ + ieee80211_ff_node_cleanup(ni); #endif /* * Allow AMPDU operation only with unencrypted traffic * or AES-CCM; the 11n spec only specifies these ciphers * so permitting any others is undefined and can lead * to interoperability problems. */ if ((ni->ni_flags & IEEE80211_NODE_HT) && (((vap->iv_flags & IEEE80211_F_WPA) && rsnparms.rsn_ucastcipher != IEEE80211_CIPHER_AES_CCM) || (vap->iv_flags & (IEEE80211_F_WPA|IEEE80211_F_PRIVACY)) == IEEE80211_F_PRIVACY)) { IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, "disallow HT use because WEP or TKIP requested, " "capinfo 0x%x ucastcipher %d", capinfo, rsnparms.rsn_ucastcipher); ieee80211_ht_node_cleanup(ni); +#ifdef IEEE80211_SUPPORT_SUPERG + /* Always do ff node cleanup; for A-MSDU */ + ieee80211_ff_node_cleanup(ni); +#endif vap->iv_stats.is_ht_assoc_downgrade++; } /* * If constrained to 11n-only stations reject legacy stations. */ if ((vap->iv_flags_ht & IEEE80211_FHT_PUREN) && (ni->ni_flags & IEEE80211_NODE_HT) == 0) { htcapmismatch(ni, wh, reassoc, resp); vap->iv_stats.is_ht_assoc_nohtcap++; return; } IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; ni->ni_intval = lintval; ni->ni_capinfo = capinfo; ni->ni_fhdwell = vap->iv_bss->ni_fhdwell; ni->ni_fhindex = vap->iv_bss->ni_fhindex; /* * Store the IEs. * XXX maybe better to just expand */ if (ieee80211_ies_init(&ni->ni_ies, sfrm, efrm - sfrm)) { #define setie(_ie, _off) ieee80211_ies_setie(ni->ni_ies, _ie, _off) if (wpa != NULL) setie(wpa_ie, wpa - sfrm); if (rsn != NULL) setie(rsn_ie, rsn - sfrm); if (htcap != NULL) setie(htcap_ie, htcap - sfrm); if (wme != NULL) { setie(wme_ie, wme - sfrm); /* * Mark node as capable of QoS. */ ni->ni_flags |= IEEE80211_NODE_QOS; } else ni->ni_flags &= ~IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (ath != NULL) { setie(ath_ie, ath - sfrm); /* * Parse ATH station parameters. */ ieee80211_parse_ath(ni, ni->ni_ies.ath_ie); } else #endif ni->ni_ath_flags = 0; #undef setie } else { ni->ni_flags &= ~IEEE80211_NODE_QOS; ni->ni_ath_flags = 0; } ieee80211_node_join(ni, resp); ieee80211_deliver_l2uf(ni); break; } case IEEE80211_FC0_SUBTYPE_DEAUTH: case IEEE80211_FC0_SUBTYPE_DISASSOC: { uint16_t reason; if (vap->iv_state != IEEE80211_S_RUN || /* NB: can happen when in promiscuous mode */ !IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { vap->iv_stats.is_rx_mgtdiscard++; break; } /* * deauth/disassoc frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); reason = le16toh(*(uint16_t *)frm); if (subtype == IEEE80211_FC0_SUBTYPE_DEAUTH) { vap->iv_stats.is_rx_deauth++; IEEE80211_NODE_STAT(ni, rx_deauth); } else { vap->iv_stats.is_rx_disassoc++; IEEE80211_NODE_STAT(ni, rx_disassoc); } IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "recv %s (reason %d)", ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], reason); if (ni != vap->iv_bss) ieee80211_node_leave(ni); break; } case IEEE80211_FC0_SUBTYPE_ACTION: case IEEE80211_FC0_SUBTYPE_ACTION_NOACK: if (ni == vap->iv_bss) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "unknown node"); vap->iv_stats.is_rx_mgtdiscard++; } else if (!IEEE80211_ADDR_EQ(vap->iv_myaddr, wh->i_addr1) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not for us"); vap->iv_stats.is_rx_mgtdiscard++; } else if (vap->iv_state != IEEE80211_S_RUN) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "wrong state %s", ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_rx_mgtdiscard++; } else { if (ieee80211_parse_action(ni, m0) == 0) (void)ic->ic_recv_action(ni, wh, frm, efrm); } break; case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: case IEEE80211_FC0_SUBTYPE_TIMING_ADV: case IEEE80211_FC0_SUBTYPE_ATIM: IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not handled"); vap->iv_stats.is_rx_mgtdiscard++; break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "mgt", "subtype 0x%x not handled", subtype); vap->iv_stats.is_rx_badsubtype++; break; } } static void hostap_recv_ctl(struct ieee80211_node *ni, struct mbuf *m, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_PS_POLL: ni->ni_vap->iv_recv_pspoll(ni, m); break; case IEEE80211_FC0_SUBTYPE_BAR: ieee80211_recv_bar(ni, m); break; } } /* * Process a received ps-poll frame. */ void ieee80211_recv_pspoll(struct ieee80211_node *ni, struct mbuf *m0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_frame_min *wh; struct mbuf *m; uint16_t aid; int qlen; wh = mtod(m0, struct ieee80211_frame_min *); if (ni->ni_associd == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, (struct ieee80211_frame *) wh, NULL, "%s", "unassociated station"); vap->iv_stats.is_ps_unassoc++; IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_NOT_ASSOCED); return; } aid = le16toh(*(uint16_t *)wh->i_dur); if (aid != ni->ni_associd) { IEEE80211_DISCARD(vap, IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, (struct ieee80211_frame *) wh, NULL, "aid mismatch: sta aid 0x%x poll aid 0x%x", ni->ni_associd, aid); vap->iv_stats.is_ps_badaid++; /* * NB: We used to deauth the station but it turns out * the Blackberry Curve 8230 (and perhaps other devices) * sometimes send the wrong AID when WME is negotiated. * Being more lenient here seems ok as we already check * the station is associated and we only return frames * queued for the station (i.e. we don't use the AID). */ return; } /* Okay, take the first queued packet and put it out... */ m = ieee80211_node_psq_dequeue(ni, &qlen); if (m == NULL) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_POWER, wh->i_addr2, "%s", "recv ps-poll, but queue empty"); ieee80211_send_nulldata(ieee80211_ref_node(ni)); vap->iv_stats.is_ps_qempty++; /* XXX node stat */ if (vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); /* just in case */ return; } /* * If there are more packets, set the more packets bit * in the packet dispatched to the station; otherwise * turn off the TIM bit. */ if (qlen != 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "recv ps-poll, send packet, %u still queued", qlen); m->m_flags |= M_MORE_DATA; } else { IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "%s", "recv ps-poll, send packet, queue empty"); if (vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); } m->m_flags |= M_PWR_SAV; /* bypass PS handling */ /* * Do the right thing; if it's an encap'ed frame then * call ieee80211_parent_xmitpkt() else * call ieee80211_vap_xmitpkt(). */ if (m->m_flags & M_ENCAP) { (void) ieee80211_parent_xmitpkt(ic, m); } else { (void) ieee80211_vap_xmitpkt(vap, m); } } Index: projects/release-pkg/sys/net80211/ieee80211_ht.h =================================================================== --- projects/release-pkg/sys/net80211/ieee80211_ht.h (revision 297604) +++ projects/release-pkg/sys/net80211/ieee80211_ht.h (revision 297605) @@ -1,221 +1,225 @@ /*- * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting * 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 _NET80211_IEEE80211_HT_H_ #define _NET80211_IEEE80211_HT_H_ /* * 802.11n protocol implementation definitions. */ #define IEEE80211_AGGR_BAWMAX 64 /* max block ack window size */ /* threshold for aging overlapping non-HT bss */ #define IEEE80211_NONHT_PRESENT_AGE msecs_to_ticks(60*1000) struct ieee80211_tx_ampdu { struct ieee80211_node *txa_ni; /* back pointer */ u_short txa_flags; #define IEEE80211_AGGR_IMMEDIATE 0x0001 /* BA policy */ #define IEEE80211_AGGR_XCHGPEND 0x0002 /* ADDBA response pending */ #define IEEE80211_AGGR_RUNNING 0x0004 /* ADDBA response received */ #define IEEE80211_AGGR_SETUP 0x0008 /* deferred state setup */ #define IEEE80211_AGGR_NAK 0x0010 /* peer NAK'd ADDBA request */ #define IEEE80211_AGGR_BARPEND 0x0020 /* BAR response pending */ uint8_t txa_tid; uint8_t txa_token; /* dialog token */ int txa_lastsample; /* ticks @ last traffic sample */ int txa_pkts; /* packets over last sample interval */ int txa_avgpps; /* filtered traffic over window */ int txa_qbytes; /* data queued (bytes) */ short txa_qframes; /* data queued (frames) */ ieee80211_seq txa_start; /* BA window left edge */ ieee80211_seq txa_seqpending; /* new txa_start pending BAR response */ uint16_t txa_wnd; /* BA window size */ uint8_t txa_attempts; /* # ADDBA/BAR requests w/o a response*/ int txa_nextrequest;/* soonest to make next request */ struct callout txa_timer; void *txa_private; /* driver-private storage */ uint64_t txa_pad[4]; }; /* return non-zero if AMPDU tx for the TID is running */ #define IEEE80211_AMPDU_RUNNING(tap) \ (((tap)->txa_flags & IEEE80211_AGGR_RUNNING) != 0) +/* return non-zero if AMPDU tx for the TID was NACKed */ +#define IEEE80211_AMPDU_NACKED(tap)\ + (!! ((tap)->txa_flags & IEEE80211_AGGR_NAK)) + /* return non-zero if AMPDU tx for the TID is running or started */ #define IEEE80211_AMPDU_REQUESTED(tap) \ (((tap)->txa_flags & \ (IEEE80211_AGGR_RUNNING|IEEE80211_AGGR_XCHGPEND|IEEE80211_AGGR_NAK)) != 0) #define IEEE80211_AGGR_BITS \ "\20\1IMMEDIATE\2XCHGPEND\3RUNNING\4SETUP\5NAK" /* * Traffic estimator support. We estimate packets/sec for * each AC that is setup for AMPDU or will potentially be * setup for AMPDU. The traffic rate can be used to decide * when AMPDU should be setup (according to a threshold) * and is available for drivers to do things like cache * eviction when only a limited number of BA streams are * available and more streams are requested than available. */ static __inline void ieee80211_txampdu_init_pps(struct ieee80211_tx_ampdu *tap) { /* * Reset packet estimate. */ tap->txa_lastsample = ticks; tap->txa_avgpps = 0; } static __inline void ieee80211_txampdu_update_pps(struct ieee80211_tx_ampdu *tap) { /* NB: scale factor of 2 was picked heuristically */ tap->txa_avgpps = ((tap->txa_avgpps << 2) - tap->txa_avgpps + tap->txa_pkts) >> 2; } /* * Count a packet towards the pps estimate. */ static __inline void ieee80211_txampdu_count_packet(struct ieee80211_tx_ampdu *tap) { /* XXX bound loop/do more crude estimate? */ while (ticks - tap->txa_lastsample >= hz) { ieee80211_txampdu_update_pps(tap); /* reset to start new sample interval */ tap->txa_pkts = 0; if (tap->txa_avgpps == 0) { tap->txa_lastsample = ticks; break; } tap->txa_lastsample += hz; } tap->txa_pkts++; } /* * Get the current pps estimate. If the average is out of * date due to lack of traffic then we decay the estimate * to account for the idle time. */ static __inline int ieee80211_txampdu_getpps(struct ieee80211_tx_ampdu *tap) { /* XXX bound loop/do more crude estimate? */ while (ticks - tap->txa_lastsample >= hz) { ieee80211_txampdu_update_pps(tap); tap->txa_pkts = 0; if (tap->txa_avgpps == 0) { tap->txa_lastsample = ticks; break; } tap->txa_lastsample += hz; } return tap->txa_avgpps; } struct ieee80211_rx_ampdu { int rxa_flags; int rxa_qbytes; /* data queued (bytes) */ short rxa_qframes; /* data queued (frames) */ ieee80211_seq rxa_seqstart; ieee80211_seq rxa_start; /* start of current BA window */ uint16_t rxa_wnd; /* BA window size */ int rxa_age; /* age of oldest frame in window */ int rxa_nframes; /* frames since ADDBA */ struct mbuf *rxa_m[IEEE80211_AGGR_BAWMAX]; void *rxa_private; uint64_t rxa_pad[3]; }; void ieee80211_ht_attach(struct ieee80211com *); void ieee80211_ht_detach(struct ieee80211com *); void ieee80211_ht_vattach(struct ieee80211vap *); void ieee80211_ht_vdetach(struct ieee80211vap *); void ieee80211_ht_announce(struct ieee80211com *); struct ieee80211_mcs_rates { uint16_t ht20_rate_800ns; uint16_t ht20_rate_400ns; uint16_t ht40_rate_800ns; uint16_t ht40_rate_400ns; }; extern const struct ieee80211_mcs_rates ieee80211_htrates[]; const struct ieee80211_htrateset *ieee80211_get_suphtrates( struct ieee80211com *, const struct ieee80211_channel *); struct ieee80211_node; int ieee80211_setup_htrates(struct ieee80211_node *, const uint8_t *htcap, int flags); void ieee80211_setup_basic_htrates(struct ieee80211_node *, const uint8_t *htinfo); struct mbuf *ieee80211_decap_amsdu(struct ieee80211_node *, struct mbuf *); int ieee80211_ampdu_reorder(struct ieee80211_node *, struct mbuf *); void ieee80211_recv_bar(struct ieee80211_node *, struct mbuf *); void ieee80211_ht_node_init(struct ieee80211_node *); void ieee80211_ht_node_cleanup(struct ieee80211_node *); void ieee80211_ht_node_age(struct ieee80211_node *); struct ieee80211_channel *ieee80211_ht_adjust_channel(struct ieee80211com *, struct ieee80211_channel *, int); void ieee80211_ht_wds_init(struct ieee80211_node *); void ieee80211_ht_node_join(struct ieee80211_node *); void ieee80211_ht_node_leave(struct ieee80211_node *); void ieee80211_htprot_update(struct ieee80211com *, int protmode); void ieee80211_ht_timeout(struct ieee80211com *); void ieee80211_parse_htcap(struct ieee80211_node *, const uint8_t *); void ieee80211_parse_htinfo(struct ieee80211_node *, const uint8_t *); int ieee80211_ht_updateparams(struct ieee80211_node *, const uint8_t *, const uint8_t *); void ieee80211_ht_updatehtcap(struct ieee80211_node *, const uint8_t *); int ieee80211_ampdu_request(struct ieee80211_node *, struct ieee80211_tx_ampdu *); void ieee80211_ampdu_stop(struct ieee80211_node *, struct ieee80211_tx_ampdu *, int); int ieee80211_send_bar(struct ieee80211_node *, struct ieee80211_tx_ampdu *, ieee80211_seq); uint8_t *ieee80211_add_htcap(uint8_t *, struct ieee80211_node *); uint8_t *ieee80211_add_htcap_vendor(uint8_t *, struct ieee80211_node *); uint8_t *ieee80211_add_htinfo(uint8_t *, struct ieee80211_node *); uint8_t *ieee80211_add_htinfo_vendor(uint8_t *, struct ieee80211_node *); struct ieee80211_beacon_offsets; void ieee80211_ht_update_beacon(struct ieee80211vap *, struct ieee80211_beacon_offsets *); int ieee80211_ampdu_rx_start_ext(struct ieee80211_node *ni, int tid, int seq, int baw); int ieee80211_ampdu_tx_request_ext(struct ieee80211_node *ni, int tid); int ieee80211_ampdu_tx_request_active_ext(struct ieee80211_node *ni, int tid, int status); #endif /* _NET80211_IEEE80211_HT_H_ */ Index: projects/release-pkg/sys/net80211/ieee80211_node.c =================================================================== --- projects/release-pkg/sys/net80211/ieee80211_node.c (revision 297604) +++ projects/release-pkg/sys/net80211/ieee80211_node.c (revision 297605) @@ -1,2828 +1,2828 @@ /*- * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting * 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 "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #ifdef IEEE80211_SUPPORT_TDMA #include #endif #include #include #include #include /* * IEEE80211_NODE_HASHSIZE must be a power of 2. */ CTASSERT((IEEE80211_NODE_HASHSIZE & (IEEE80211_NODE_HASHSIZE-1)) == 0); /* * Association id's are managed with a bit vector. */ #define IEEE80211_AID_SET(_vap, b) \ ((_vap)->iv_aid_bitmap[IEEE80211_AID(b) / 32] |= \ (1 << (IEEE80211_AID(b) % 32))) #define IEEE80211_AID_CLR(_vap, b) \ ((_vap)->iv_aid_bitmap[IEEE80211_AID(b) / 32] &= \ ~(1 << (IEEE80211_AID(b) % 32))) #define IEEE80211_AID_ISSET(_vap, b) \ ((_vap)->iv_aid_bitmap[IEEE80211_AID(b) / 32] & (1 << (IEEE80211_AID(b) % 32))) #ifdef IEEE80211_DEBUG_REFCNT #define REFCNT_LOC "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line #else #define REFCNT_LOC "%s %p<%s> refcnt %d\n", __func__ #endif static int ieee80211_sta_join1(struct ieee80211_node *); static struct ieee80211_node *node_alloc(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN]); static void node_cleanup(struct ieee80211_node *); static void node_free(struct ieee80211_node *); static void node_age(struct ieee80211_node *); static int8_t node_getrssi(const struct ieee80211_node *); static void node_getsignal(const struct ieee80211_node *, int8_t *, int8_t *); static void node_getmimoinfo(const struct ieee80211_node *, struct ieee80211_mimo_info *); static void _ieee80211_free_node(struct ieee80211_node *); static void node_reclaim(struct ieee80211_node_table *nt, struct ieee80211_node *ni); static void ieee80211_node_table_init(struct ieee80211com *ic, struct ieee80211_node_table *nt, const char *name, int inact, int keymaxix); static void ieee80211_node_table_reset(struct ieee80211_node_table *, struct ieee80211vap *); static void ieee80211_node_table_cleanup(struct ieee80211_node_table *nt); static void ieee80211_erp_timeout(struct ieee80211com *); MALLOC_DEFINE(M_80211_NODE, "80211node", "802.11 node state"); MALLOC_DEFINE(M_80211_NODE_IE, "80211nodeie", "802.11 node ie"); void ieee80211_node_attach(struct ieee80211com *ic) { /* XXX really want maxlen enforced per-sta */ ieee80211_ageq_init(&ic->ic_stageq, ic->ic_max_keyix * 8, "802.11 staging q"); ieee80211_node_table_init(ic, &ic->ic_sta, "station", IEEE80211_INACT_INIT, ic->ic_max_keyix); callout_init(&ic->ic_inact, 1); callout_reset(&ic->ic_inact, IEEE80211_INACT_WAIT*hz, ieee80211_node_timeout, ic); ic->ic_node_alloc = node_alloc; ic->ic_node_free = node_free; ic->ic_node_cleanup = node_cleanup; ic->ic_node_age = node_age; ic->ic_node_drain = node_age; /* NB: same as age */ ic->ic_node_getrssi = node_getrssi; ic->ic_node_getsignal = node_getsignal; ic->ic_node_getmimoinfo = node_getmimoinfo; /* * Set flags to be propagated to all vap's; * these define default behaviour/configuration. */ ic->ic_flags_ext |= IEEE80211_FEXT_INACT; /* inactivity processing */ } void ieee80211_node_detach(struct ieee80211com *ic) { callout_drain(&ic->ic_inact); ieee80211_node_table_cleanup(&ic->ic_sta); ieee80211_ageq_cleanup(&ic->ic_stageq); } void ieee80211_node_vattach(struct ieee80211vap *vap) { /* NB: driver can override */ vap->iv_max_aid = IEEE80211_AID_DEF; /* default station inactivity timer setings */ vap->iv_inact_init = IEEE80211_INACT_INIT; vap->iv_inact_auth = IEEE80211_INACT_AUTH; vap->iv_inact_run = IEEE80211_INACT_RUN; vap->iv_inact_probe = IEEE80211_INACT_PROBE; IEEE80211_DPRINTF(vap, IEEE80211_MSG_INACT, "%s: init %u auth %u run %u probe %u\n", __func__, vap->iv_inact_init, vap->iv_inact_auth, vap->iv_inact_run, vap->iv_inact_probe); } void ieee80211_node_latevattach(struct ieee80211vap *vap) { if (vap->iv_opmode == IEEE80211_M_HOSTAP) { /* XXX should we allow max aid to be zero? */ if (vap->iv_max_aid < IEEE80211_AID_MIN) { vap->iv_max_aid = IEEE80211_AID_MIN; if_printf(vap->iv_ifp, "WARNING: max aid too small, changed to %d\n", vap->iv_max_aid); } vap->iv_aid_bitmap = (uint32_t *) IEEE80211_MALLOC( howmany(vap->iv_max_aid, 32) * sizeof(uint32_t), M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (vap->iv_aid_bitmap == NULL) { /* XXX no way to recover */ printf("%s: no memory for AID bitmap, max aid %d!\n", __func__, vap->iv_max_aid); vap->iv_max_aid = 0; } } ieee80211_reset_bss(vap); vap->iv_auth = ieee80211_authenticator_get(vap->iv_bss->ni_authmode); } void ieee80211_node_vdetach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; ieee80211_node_table_reset(&ic->ic_sta, vap); if (vap->iv_bss != NULL) { ieee80211_free_node(vap->iv_bss); vap->iv_bss = NULL; } if (vap->iv_aid_bitmap != NULL) { IEEE80211_FREE(vap->iv_aid_bitmap, M_80211_NODE); vap->iv_aid_bitmap = NULL; } } /* * Port authorize/unauthorize interfaces for use by an authenticator. */ void ieee80211_node_authorize(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; ni->ni_flags |= IEEE80211_NODE_AUTH; ni->ni_inact_reload = vap->iv_inact_run; ni->ni_inact = ni->ni_inact_reload; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact_reload %u", __func__, ni->ni_inact_reload); } void ieee80211_node_unauthorize(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; ni->ni_flags &= ~IEEE80211_NODE_AUTH; ni->ni_inact_reload = vap->iv_inact_auth; if (ni->ni_inact > ni->ni_inact_reload) ni->ni_inact = ni->ni_inact_reload; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact_reload %u inact %u", __func__, ni->ni_inact_reload, ni->ni_inact); } /* * Fix tx parameters for a node according to ``association state''. */ void ieee80211_node_setuptxparms(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; enum ieee80211_phymode mode; if (ni->ni_flags & IEEE80211_NODE_HT) { if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) mode = IEEE80211_MODE_11NA; else mode = IEEE80211_MODE_11NG; } else { /* legacy rate handling */ if (IEEE80211_IS_CHAN_ST(ni->ni_chan)) mode = IEEE80211_MODE_STURBO_A; else if (IEEE80211_IS_CHAN_HALF(ni->ni_chan)) mode = IEEE80211_MODE_HALF; else if (IEEE80211_IS_CHAN_QUARTER(ni->ni_chan)) mode = IEEE80211_MODE_QUARTER; /* NB: 108A should be handled as 11a */ else if (IEEE80211_IS_CHAN_A(ni->ni_chan)) mode = IEEE80211_MODE_11A; else if (IEEE80211_IS_CHAN_108G(ni->ni_chan) || (ni->ni_flags & IEEE80211_NODE_ERP)) mode = IEEE80211_MODE_11G; else mode = IEEE80211_MODE_11B; } ni->ni_txparms = &vap->iv_txparms[mode]; } /* * Set/change the channel. The rate set is also updated as * to insure a consistent view by drivers. * XXX should be private but hostap needs it to deal with CSA */ void ieee80211_node_set_chan(struct ieee80211_node *ni, struct ieee80211_channel *chan) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; enum ieee80211_phymode mode; KASSERT(chan != IEEE80211_CHAN_ANYC, ("no channel")); ni->ni_chan = chan; mode = ieee80211_chan2mode(chan); if (IEEE80211_IS_CHAN_HT(chan)) { /* * We must install the legacy rate est in ni_rates and the * HT rate set in ni_htrates. */ ni->ni_htrates = *ieee80211_get_suphtrates(ic, chan); /* * Setup bss tx parameters based on operating mode. We * use legacy rates when operating in a mixed HT+non-HT bss * and non-ERP rates in 11g for mixed ERP+non-ERP bss. */ if (mode == IEEE80211_MODE_11NA && (vap->iv_flags_ht & IEEE80211_FHT_PUREN) == 0) mode = IEEE80211_MODE_11A; else if (mode == IEEE80211_MODE_11NG && (vap->iv_flags_ht & IEEE80211_FHT_PUREN) == 0) mode = IEEE80211_MODE_11G; if (mode == IEEE80211_MODE_11G && (vap->iv_flags & IEEE80211_F_PUREG) == 0) mode = IEEE80211_MODE_11B; } ni->ni_txparms = &vap->iv_txparms[mode]; ni->ni_rates = *ieee80211_get_suprates(ic, chan); } static __inline void copy_bss(struct ieee80211_node *nbss, const struct ieee80211_node *obss) { /* propagate useful state */ nbss->ni_authmode = obss->ni_authmode; nbss->ni_txpower = obss->ni_txpower; nbss->ni_vlan = obss->ni_vlan; /* XXX statistics? */ /* XXX legacy WDS bssid? */ } void ieee80211_create_ibss(struct ieee80211vap* vap, struct ieee80211_channel *chan) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: creating %s on channel %u\n", __func__, ieee80211_opmode_name[vap->iv_opmode], ieee80211_chan2ieee(ic, chan)); ni = ieee80211_alloc_node(&ic->ic_sta, vap, vap->iv_myaddr); if (ni == NULL) { /* XXX recovery? */ return; } IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_myaddr); ni->ni_esslen = vap->iv_des_ssid[0].len; memcpy(ni->ni_essid, vap->iv_des_ssid[0].ssid, ni->ni_esslen); if (vap->iv_bss != NULL) copy_bss(ni, vap->iv_bss); ni->ni_intval = ic->ic_bintval; if (vap->iv_flags & IEEE80211_F_PRIVACY) ni->ni_capinfo |= IEEE80211_CAPINFO_PRIVACY; if (ic->ic_phytype == IEEE80211_T_FH) { ni->ni_fhdwell = 200; /* XXX */ ni->ni_fhindex = 1; } if (vap->iv_opmode == IEEE80211_M_IBSS) { vap->iv_flags |= IEEE80211_F_SIBSS; ni->ni_capinfo |= IEEE80211_CAPINFO_IBSS; /* XXX */ if (vap->iv_flags & IEEE80211_F_DESBSSID) IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_des_bssid); else { get_random_bytes(ni->ni_bssid, IEEE80211_ADDR_LEN); /* clear group bit, add local bit */ ni->ni_bssid[0] = (ni->ni_bssid[0] &~ 0x01) | 0x02; } } else if (vap->iv_opmode == IEEE80211_M_AHDEMO) { if (vap->iv_flags & IEEE80211_F_DESBSSID) IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_des_bssid); else #ifdef IEEE80211_SUPPORT_TDMA if ((vap->iv_caps & IEEE80211_C_TDMA) == 0) #endif memset(ni->ni_bssid, 0, IEEE80211_ADDR_LEN); #ifdef IEEE80211_SUPPORT_MESH } else if (vap->iv_opmode == IEEE80211_M_MBSS) { ni->ni_meshidlen = vap->iv_mesh->ms_idlen; memcpy(ni->ni_meshid, vap->iv_mesh->ms_id, ni->ni_meshidlen); #endif } /* * Fix the channel and related attributes. */ /* clear DFS CAC state on previous channel */ if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && ic->ic_bsschan->ic_freq != chan->ic_freq && IEEE80211_IS_CHAN_CACDONE(ic->ic_bsschan)) ieee80211_dfs_cac_clear(ic, ic->ic_bsschan); ic->ic_bsschan = chan; ieee80211_node_set_chan(ni, chan); ic->ic_curmode = ieee80211_chan2mode(chan); /* * Do mode-specific setup. */ if (IEEE80211_IS_CHAN_FULL(chan)) { if (IEEE80211_IS_CHAN_ANYG(chan)) { /* * Use a mixed 11b/11g basic rate set. */ ieee80211_setbasicrates(&ni->ni_rates, IEEE80211_MODE_11G); if (vap->iv_flags & IEEE80211_F_PUREG) { /* * Also mark OFDM rates basic so 11b * stations do not join (WiFi compliance). */ ieee80211_addbasicrates(&ni->ni_rates, IEEE80211_MODE_11A); } } else if (IEEE80211_IS_CHAN_B(chan)) { /* * Force pure 11b rate set. */ ieee80211_setbasicrates(&ni->ni_rates, IEEE80211_MODE_11B); } } (void) ieee80211_sta_join1(ieee80211_ref_node(ni)); } /* * Reset bss state on transition to the INIT state. * Clear any stations from the table (they have been * deauth'd) and reset the bss node (clears key, rate * etc. state). */ void ieee80211_reset_bss(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni, *obss; ieee80211_node_table_reset(&ic->ic_sta, vap); /* XXX multi-bss: wrong */ ieee80211_reset_erp(ic); ni = ieee80211_alloc_node(&ic->ic_sta, vap, vap->iv_myaddr); KASSERT(ni != NULL, ("unable to setup initial BSS node")); obss = vap->iv_bss; vap->iv_bss = ieee80211_ref_node(ni); if (obss != NULL) { copy_bss(ni, obss); ni->ni_intval = ic->ic_bintval; ieee80211_free_node(obss); } else IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_myaddr); } static int match_ssid(const struct ieee80211_node *ni, int nssid, const struct ieee80211_scan_ssid ssids[]) { int i; for (i = 0; i < nssid; i++) { if (ni->ni_esslen == ssids[i].len && memcmp(ni->ni_essid, ssids[i].ssid, ni->ni_esslen) == 0) return 1; } return 0; } /* * Test a node for suitability/compatibility. */ static int check_bss(struct ieee80211vap *vap, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; uint8_t rate; if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, ni->ni_chan))) return 0; if (vap->iv_opmode == IEEE80211_M_IBSS) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) return 0; } else { if ((ni->ni_capinfo & IEEE80211_CAPINFO_ESS) == 0) return 0; } if (vap->iv_flags & IEEE80211_F_PRIVACY) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) return 0; } else { /* XXX does this mean privacy is supported or required? */ if (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) return 0; } rate = ieee80211_fix_rate(ni, &ni->ni_rates, IEEE80211_F_JOIN | IEEE80211_F_DONEGO | IEEE80211_F_DOFRATE); if (rate & IEEE80211_RATE_BASIC) return 0; if (vap->iv_des_nssid != 0 && !match_ssid(ni, vap->iv_des_nssid, vap->iv_des_ssid)) return 0; if ((vap->iv_flags & IEEE80211_F_DESBSSID) && !IEEE80211_ADDR_EQ(vap->iv_des_bssid, ni->ni_bssid)) return 0; return 1; } #ifdef IEEE80211_DEBUG /* * Display node suitability/compatibility. */ static void check_bss_debug(struct ieee80211vap *vap, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; uint8_t rate; int fail; fail = 0; if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, ni->ni_chan))) fail |= 0x01; if (vap->iv_opmode == IEEE80211_M_IBSS) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) fail |= 0x02; } else { if ((ni->ni_capinfo & IEEE80211_CAPINFO_ESS) == 0) fail |= 0x02; } if (vap->iv_flags & IEEE80211_F_PRIVACY) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) fail |= 0x04; } else { /* XXX does this mean privacy is supported or required? */ if (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) fail |= 0x04; } rate = ieee80211_fix_rate(ni, &ni->ni_rates, IEEE80211_F_JOIN | IEEE80211_F_DONEGO | IEEE80211_F_DOFRATE); if (rate & IEEE80211_RATE_BASIC) fail |= 0x08; if (vap->iv_des_nssid != 0 && !match_ssid(ni, vap->iv_des_nssid, vap->iv_des_ssid)) fail |= 0x10; if ((vap->iv_flags & IEEE80211_F_DESBSSID) && !IEEE80211_ADDR_EQ(vap->iv_des_bssid, ni->ni_bssid)) fail |= 0x20; printf(" %c %s", fail ? '-' : '+', ether_sprintf(ni->ni_macaddr)); printf(" %s%c", ether_sprintf(ni->ni_bssid), fail & 0x20 ? '!' : ' '); printf(" %3d%c", ieee80211_chan2ieee(ic, ni->ni_chan), fail & 0x01 ? '!' : ' '); printf(" %2dM%c", (rate & IEEE80211_RATE_VAL) / 2, fail & 0x08 ? '!' : ' '); printf(" %4s%c", (ni->ni_capinfo & IEEE80211_CAPINFO_ESS) ? "ess" : (ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) ? "ibss" : "????", fail & 0x02 ? '!' : ' '); printf(" %3s%c ", (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) ? "wep" : "no", fail & 0x04 ? '!' : ' '); ieee80211_print_essid(ni->ni_essid, ni->ni_esslen); printf("%s\n", fail & 0x10 ? "!" : ""); } #endif /* IEEE80211_DEBUG */ /* * Handle 802.11 ad hoc network merge. The * convention, set by the Wireless Ethernet Compatibility Alliance * (WECA), is that an 802.11 station will change its BSSID to match * the "oldest" 802.11 ad hoc network, on the same channel, that * has the station's desired SSID. The "oldest" 802.11 network * sends beacons with the greatest TSF timestamp. * * The caller is assumed to validate TSF's before attempting a merge. * * Return !0 if the BSSID changed, 0 otherwise. */ int ieee80211_ibss_merge(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; #ifdef IEEE80211_DEBUG struct ieee80211com *ic = ni->ni_ic; #endif if (ni == vap->iv_bss || IEEE80211_ADDR_EQ(ni->ni_bssid, vap->iv_bss->ni_bssid)) { /* unchanged, nothing to do */ return 0; } if (!check_bss(vap, ni)) { /* capabilities mismatch */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s: merge failed, capabilities mismatch\n", __func__); #ifdef IEEE80211_DEBUG if (ieee80211_msg_assoc(vap)) check_bss_debug(vap, ni); #endif vap->iv_stats.is_ibss_capmismatch++; return 0; } IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s: new bssid %s: %s preamble, %s slot time%s\n", __func__, ether_sprintf(ni->ni_bssid), ic->ic_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long", ic->ic_flags&IEEE80211_F_SHSLOT ? "short" : "long", ic->ic_flags&IEEE80211_F_USEPROT ? ", protection" : "" ); return ieee80211_sta_join1(ieee80211_ref_node(ni)); } /* * Calculate HT channel promotion flags for all vaps. * This assumes ni_chan have been setup for each vap. */ static int gethtadjustflags(struct ieee80211com *ic) { struct ieee80211vap *vap; int flags; flags = 0; /* XXX locking */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (vap->iv_state < IEEE80211_S_RUN) continue; switch (vap->iv_opmode) { case IEEE80211_M_WDS: case IEEE80211_M_STA: case IEEE80211_M_AHDEMO: case IEEE80211_M_HOSTAP: case IEEE80211_M_IBSS: case IEEE80211_M_MBSS: flags |= ieee80211_htchanflags(vap->iv_bss->ni_chan); break; default: break; } } return flags; } /* * Check if the current channel needs to change based on whether * any vap's are using HT20/HT40. This is used to sync the state * of ic_curchan after a channel width change on a running vap. */ void ieee80211_sync_curchan(struct ieee80211com *ic) { struct ieee80211_channel *c; c = ieee80211_ht_adjust_channel(ic, ic->ic_curchan, gethtadjustflags(ic)); if (c != ic->ic_curchan) { ic->ic_curchan = c; ic->ic_curmode = ieee80211_chan2mode(ic->ic_curchan); ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); IEEE80211_UNLOCK(ic); ic->ic_set_channel(ic); ieee80211_radiotap_chan_change(ic); IEEE80211_LOCK(ic); } } /* * Setup the current channel. The request channel may be * promoted if other vap's are operating with HT20/HT40. */ void ieee80211_setupcurchan(struct ieee80211com *ic, struct ieee80211_channel *c) { if (ic->ic_htcaps & IEEE80211_HTC_HT) { int flags = gethtadjustflags(ic); /* * Check for channel promotion required to support the * set of running vap's. This assumes we are called * after ni_chan is setup for each vap. */ /* NB: this assumes IEEE80211_FHT_USEHT40 > IEEE80211_FHT_HT */ if (flags > ieee80211_htchanflags(c)) c = ieee80211_ht_adjust_channel(ic, c, flags); } ic->ic_bsschan = ic->ic_curchan = c; ic->ic_curmode = ieee80211_chan2mode(ic->ic_curchan); ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); } /* * Change the current channel. The channel change is guaranteed to have * happened before the next state change. */ void ieee80211_setcurchan(struct ieee80211com *ic, struct ieee80211_channel *c) { ieee80211_setupcurchan(ic, c); ieee80211_runtask(ic, &ic->ic_chan_task); } void ieee80211_update_chw(struct ieee80211com *ic) { ieee80211_setupcurchan(ic, ic->ic_curchan); ieee80211_runtask(ic, &ic->ic_chw_task); } /* * Join the specified IBSS/BSS network. The node is assumed to * be passed in with a held reference. */ static int ieee80211_sta_join1(struct ieee80211_node *selbs) { struct ieee80211vap *vap = selbs->ni_vap; struct ieee80211com *ic = selbs->ni_ic; struct ieee80211_node *obss; int canreassoc; /* * Committed to selbs, setup state. */ obss = vap->iv_bss; /* * Check if old+new node have the same address in which * case we can reassociate when operating in sta mode. */ canreassoc = (obss != NULL && vap->iv_state == IEEE80211_S_RUN && IEEE80211_ADDR_EQ(obss->ni_macaddr, selbs->ni_macaddr)); vap->iv_bss = selbs; /* NB: caller assumed to bump refcnt */ if (obss != NULL) { struct ieee80211_node_table *nt = obss->ni_table; copy_bss(selbs, obss); ieee80211_node_decref(obss); /* iv_bss reference */ IEEE80211_NODE_LOCK(nt); node_reclaim(nt, obss); /* station table reference */ IEEE80211_NODE_UNLOCK(nt); obss = NULL; /* NB: guard against later use */ } /* * Delete unusable rates; we've already checked * that the negotiated rate set is acceptable. */ ieee80211_fix_rate(vap->iv_bss, &vap->iv_bss->ni_rates, IEEE80211_F_DODEL | IEEE80211_F_JOIN); ieee80211_setcurchan(ic, selbs->ni_chan); /* * Set the erp state (mostly the slot time) to deal with * the auto-select case; this should be redundant if the * mode is locked. */ ieee80211_reset_erp(ic); ieee80211_wme_initparams(vap); if (vap->iv_opmode == IEEE80211_M_STA) { if (canreassoc) { /* Reassociate */ ieee80211_new_state(vap, IEEE80211_S_ASSOC, 1); } else { /* * Act as if we received a DEAUTH frame in case we * are invoked from the RUN state. This will cause * us to try to re-authenticate if we are operating * as a station. */ ieee80211_new_state(vap, IEEE80211_S_AUTH, IEEE80211_FC0_SUBTYPE_DEAUTH); } } else ieee80211_new_state(vap, IEEE80211_S_RUN, -1); return 1; } int ieee80211_sta_join(struct ieee80211vap *vap, struct ieee80211_channel *chan, const struct ieee80211_scan_entry *se) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; ni = ieee80211_alloc_node(&ic->ic_sta, vap, se->se_macaddr); if (ni == NULL) { /* XXX msg */ return 0; } /* * Expand scan state into node's format. * XXX may not need all this stuff */ IEEE80211_ADDR_COPY(ni->ni_bssid, se->se_bssid); ni->ni_esslen = se->se_ssid[1]; memcpy(ni->ni_essid, se->se_ssid+2, ni->ni_esslen); ni->ni_tstamp.tsf = se->se_tstamp.tsf; ni->ni_intval = se->se_intval; ni->ni_capinfo = se->se_capinfo; ni->ni_chan = chan; ni->ni_timoff = se->se_timoff; ni->ni_fhdwell = se->se_fhdwell; ni->ni_fhindex = se->se_fhindex; ni->ni_erp = se->se_erp; IEEE80211_RSSI_LPF(ni->ni_avgrssi, se->se_rssi); ni->ni_noise = se->se_noise; if (vap->iv_opmode == IEEE80211_M_STA) { /* NB: only infrastructure mode requires an associd */ ni->ni_flags |= IEEE80211_NODE_ASSOCID; } if (ieee80211_ies_init(&ni->ni_ies, se->se_ies.data, se->se_ies.len)) { ieee80211_ies_expand(&ni->ni_ies); #ifdef IEEE80211_SUPPORT_SUPERG if (ni->ni_ies.ath_ie != NULL) ieee80211_parse_ath(ni, ni->ni_ies.ath_ie); #endif if (ni->ni_ies.htcap_ie != NULL) ieee80211_parse_htcap(ni, ni->ni_ies.htcap_ie); if (ni->ni_ies.htinfo_ie != NULL) ieee80211_parse_htinfo(ni, ni->ni_ies.htinfo_ie); #ifdef IEEE80211_SUPPORT_MESH if (ni->ni_ies.meshid_ie != NULL) ieee80211_parse_meshid(ni, ni->ni_ies.meshid_ie); #endif #ifdef IEEE80211_SUPPORT_TDMA if (ni->ni_ies.tdma_ie != NULL) ieee80211_parse_tdma(ni, ni->ni_ies.tdma_ie); #endif } vap->iv_dtim_period = se->se_dtimperiod; vap->iv_dtim_count = 0; /* NB: must be after ni_chan is setup */ ieee80211_setup_rates(ni, se->se_rates, se->se_xrates, IEEE80211_F_DOSORT); if (ieee80211_iserp_rateset(&ni->ni_rates)) ni->ni_flags |= IEEE80211_NODE_ERP; /* * Setup HT state for this node if it's available, otherwise * non-STA modes won't pick this state up. * * For IBSS and related modes that don't go through an * association request/response, the only appropriate place * to setup the HT state is here. */ if (ni->ni_ies.htinfo_ie != NULL && ni->ni_ies.htcap_ie != NULL && vap->iv_flags_ht & IEEE80211_FHT_HT) { ieee80211_ht_node_init(ni); ieee80211_ht_updateparams(ni, ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); ieee80211_setup_htrates(ni, ni->ni_ies.htcap_ie, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, ni->ni_ies.htinfo_ie); } /* XXX else check for ath FF? */ /* XXX QoS? Difficult given that WME config is specific to a master */ ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); return ieee80211_sta_join1(ieee80211_ref_node(ni)); } /* * Leave the specified IBSS/BSS network. The node is assumed to * be passed in with a held reference. */ void ieee80211_sta_leave(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; ic->ic_node_cleanup(ni); ieee80211_notify_node_leave(ni); } /* * Send a deauthenticate frame and drop the station. */ void ieee80211_node_deauth(struct ieee80211_node *ni, int reason) { /* NB: bump the refcnt to be sure temporary nodes are not reclaimed */ ieee80211_ref_node(ni); if (ni->ni_associd != 0) IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, reason); ieee80211_node_leave(ni); ieee80211_free_node(ni); } static struct ieee80211_node * node_alloc(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211_node *ni; ni = (struct ieee80211_node *) IEEE80211_MALLOC(sizeof(struct ieee80211_node), M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); return ni; } /* * Initialize an ie blob with the specified data. If previous * data exists re-use the data block. As a side effect we clear * all references to specific ie's; the caller is required to * recalculate them. */ int ieee80211_ies_init(struct ieee80211_ies *ies, const uint8_t *data, int len) { /* NB: assumes data+len are the last fields */ memset(ies, 0, offsetof(struct ieee80211_ies, data)); if (ies->data != NULL && ies->len != len) { /* data size changed */ IEEE80211_FREE(ies->data, M_80211_NODE_IE); ies->data = NULL; } if (ies->data == NULL) { ies->data = (uint8_t *) IEEE80211_MALLOC(len, M_80211_NODE_IE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (ies->data == NULL) { ies->len = 0; /* NB: pointers have already been zero'd above */ return 0; } } memcpy(ies->data, data, len); ies->len = len; return 1; } /* * Reclaim storage for an ie blob. */ void ieee80211_ies_cleanup(struct ieee80211_ies *ies) { if (ies->data != NULL) IEEE80211_FREE(ies->data, M_80211_NODE_IE); } /* * Expand an ie blob data contents and to fillin individual * ie pointers. The data blob is assumed to be well-formed; * we don't do any validity checking of ie lengths. */ void ieee80211_ies_expand(struct ieee80211_ies *ies) { uint8_t *ie; int ielen; ie = ies->data; ielen = ies->len; while (ielen > 0) { switch (ie[0]) { case IEEE80211_ELEMID_VENDOR: if (iswpaoui(ie)) ies->wpa_ie = ie; else if (iswmeoui(ie)) ies->wme_ie = ie; #ifdef IEEE80211_SUPPORT_SUPERG else if (isatherosoui(ie)) ies->ath_ie = ie; #endif #ifdef IEEE80211_SUPPORT_TDMA else if (istdmaoui(ie)) ies->tdma_ie = ie; #endif break; case IEEE80211_ELEMID_RSN: ies->rsn_ie = ie; break; case IEEE80211_ELEMID_HTCAP: ies->htcap_ie = ie; break; case IEEE80211_ELEMID_HTINFO: ies->htinfo_ie = ie; break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_ELEMID_MESHID: ies->meshid_ie = ie; break; #endif } ielen -= 2 + ie[1]; ie += 2 + ie[1]; } } /* * Reclaim any resources in a node and reset any critical * state. Typically nodes are free'd immediately after, * but in some cases the storage may be reused so we need * to insure consistent state (should probably fix that). */ static void node_cleanup(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; int i; /* NB: preserve ni_table */ if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) { if (vap->iv_opmode != IEEE80211_M_STA) vap->iv_ps_sta--; ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "power save mode off, %u sta's in ps mode", vap->iv_ps_sta); } /* * Cleanup any HT-related state. */ if (ni->ni_flags & IEEE80211_NODE_HT) ieee80211_ht_node_cleanup(ni); #ifdef IEEE80211_SUPPORT_SUPERG - else if (ni->ni_ath_flags & IEEE80211_NODE_ATH) - ieee80211_ff_node_cleanup(ni); + /* Always do FF node cleanup; for A-MSDU */ + ieee80211_ff_node_cleanup(ni); #endif #ifdef IEEE80211_SUPPORT_MESH /* * Cleanup any mesh-related state. */ if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_node_cleanup(ni); #endif /* * Clear any staging queue entries. */ ieee80211_ageq_drain_node(&ic->ic_stageq, ni); /* * Clear AREF flag that marks the authorization refcnt bump * has happened. This is probably not needed as the node * should always be removed from the table so not found but * do it just in case. * Likewise clear the ASSOCID flag as these flags are intended * to be managed in tandem. */ ni->ni_flags &= ~(IEEE80211_NODE_AREF | IEEE80211_NODE_ASSOCID); /* * Drain power save queue and, if needed, clear TIM. */ if (ieee80211_node_psq_drain(ni) != 0 && vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); ni->ni_associd = 0; if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } /* * Preserve SSID, WPA, and WME ie's so the bss node is * reusable during a re-auth/re-assoc state transition. * If we remove these data they will not be recreated * because they come from a probe-response or beacon frame * which cannot be expected prior to the association-response. * This should not be an issue when operating in other modes * as stations leaving always go through a full state transition * which will rebuild this state. * * XXX does this leave us open to inheriting old state? */ for (i = 0; i < nitems(ni->ni_rxfrag); i++) if (ni->ni_rxfrag[i] != NULL) { m_freem(ni->ni_rxfrag[i]); ni->ni_rxfrag[i] = NULL; } /* * Must be careful here to remove any key map entry w/o a LOR. */ ieee80211_node_delucastkey(ni); } static void node_free(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; ieee80211_ratectl_node_deinit(ni); ic->ic_node_cleanup(ni); ieee80211_ies_cleanup(&ni->ni_ies); ieee80211_psq_cleanup(&ni->ni_psq); IEEE80211_FREE(ni, M_80211_NODE); } static void node_age(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; IEEE80211_NODE_LOCK_ASSERT(&vap->iv_ic->ic_sta); /* * Age frames on the power save queue. */ if (ieee80211_node_psq_age(ni) != 0 && ni->ni_psq.psq_len == 0 && vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); /* * Age out HT resources (e.g. frames on the * A-MPDU reorder queues). */ if (ni->ni_associd != 0 && (ni->ni_flags & IEEE80211_NODE_HT)) ieee80211_ht_node_age(ni); } static int8_t node_getrssi(const struct ieee80211_node *ni) { uint32_t avgrssi = ni->ni_avgrssi; int32_t rssi; if (avgrssi == IEEE80211_RSSI_DUMMY_MARKER) return 0; rssi = IEEE80211_RSSI_GET(avgrssi); return rssi < 0 ? 0 : rssi > 127 ? 127 : rssi; } static void node_getsignal(const struct ieee80211_node *ni, int8_t *rssi, int8_t *noise) { *rssi = node_getrssi(ni); *noise = ni->ni_noise; } static void node_getmimoinfo(const struct ieee80211_node *ni, struct ieee80211_mimo_info *info) { int i; uint32_t avgrssi; int32_t rssi; bzero(info, sizeof(*info)); for (i = 0; i < ni->ni_mimo_chains; i++) { avgrssi = ni->ni_mimo_rssi_ctl[i]; if (avgrssi == IEEE80211_RSSI_DUMMY_MARKER) { info->rssi[i] = 0; } else { rssi = IEEE80211_RSSI_GET(avgrssi); info->rssi[i] = rssi < 0 ? 0 : rssi > 127 ? 127 : rssi; } info->noise[i] = ni->ni_mimo_noise_ctl[i]; } /* XXX ext radios? */ /* XXX EVM? */ } struct ieee80211_node * ieee80211_alloc_node(struct ieee80211_node_table *nt, struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211com *ic = nt->nt_ic; struct ieee80211_node *ni; int hash; ni = ic->ic_node_alloc(vap, macaddr); if (ni == NULL) { vap->iv_stats.is_rx_nodealloc++; return NULL; } IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s %p<%s> in %s table\n", __func__, ni, ether_sprintf(macaddr), nt->nt_name); IEEE80211_ADDR_COPY(ni->ni_macaddr, macaddr); hash = IEEE80211_NODE_HASH(ic, macaddr); ieee80211_node_initref(ni); /* mark referenced */ ni->ni_chan = IEEE80211_CHAN_ANYC; ni->ni_authmode = IEEE80211_AUTH_OPEN; ni->ni_txpower = ic->ic_txpowlimit; /* max power */ ni->ni_txparms = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; ieee80211_crypto_resetkey(vap, &ni->ni_ucastkey, IEEE80211_KEYIX_NONE); ni->ni_avgrssi = IEEE80211_RSSI_DUMMY_MARKER; ni->ni_inact_reload = nt->nt_inact_init; ni->ni_inact = ni->ni_inact_reload; ni->ni_ath_defkeyix = 0x7fff; ieee80211_psq_init(&ni->ni_psq, "unknown"); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_node_init(vap, ni); #endif IEEE80211_NODE_LOCK(nt); TAILQ_INSERT_TAIL(&nt->nt_node, ni, ni_list); LIST_INSERT_HEAD(&nt->nt_hash[hash], ni, ni_hash); ni->ni_table = nt; ni->ni_vap = vap; ni->ni_ic = ic; IEEE80211_NODE_UNLOCK(nt); IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact_reload %u", __func__, ni->ni_inact_reload); ieee80211_ratectl_node_init(ni); return ni; } /* * Craft a temporary node suitable for sending a management frame * to the specified station. We craft only as much state as we * need to do the work since the node will be immediately reclaimed * once the send completes. */ struct ieee80211_node * ieee80211_tmp_node(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; ni = ic->ic_node_alloc(vap, macaddr); if (ni != NULL) { struct ieee80211_node *bss = vap->iv_bss; IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s %p<%s>\n", __func__, ni, ether_sprintf(macaddr)); ni->ni_table = NULL; /* NB: pedantic */ ni->ni_ic = ic; /* NB: needed to set channel */ ni->ni_vap = vap; IEEE80211_ADDR_COPY(ni->ni_macaddr, macaddr); IEEE80211_ADDR_COPY(ni->ni_bssid, bss->ni_bssid); ieee80211_node_initref(ni); /* mark referenced */ /* NB: required by ieee80211_fix_rate */ ieee80211_node_set_chan(ni, bss->ni_chan); ieee80211_crypto_resetkey(vap, &ni->ni_ucastkey, IEEE80211_KEYIX_NONE); ni->ni_txpower = bss->ni_txpower; /* XXX optimize away */ ieee80211_psq_init(&ni->ni_psq, "unknown"); ieee80211_ratectl_node_init(ni); } else { /* XXX msg */ vap->iv_stats.is_rx_nodealloc++; } return ni; } struct ieee80211_node * ieee80211_dup_bss(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; ni = ieee80211_alloc_node(&ic->ic_sta, vap, macaddr); if (ni != NULL) { struct ieee80211_node *bss = vap->iv_bss; /* * Inherit from iv_bss. */ copy_bss(ni, bss); IEEE80211_ADDR_COPY(ni->ni_bssid, bss->ni_bssid); ieee80211_node_set_chan(ni, bss->ni_chan); } return ni; } /* * Create a bss node for a legacy WDS vap. The far end does * not associate so we just create create a new node and * simulate an association. The caller is responsible for * installing the node as the bss node and handling any further * setup work like authorizing the port. */ struct ieee80211_node * ieee80211_node_create_wds(struct ieee80211vap *vap, const uint8_t bssid[IEEE80211_ADDR_LEN], struct ieee80211_channel *chan) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; /* XXX check if node already in sta table? */ ni = ieee80211_alloc_node(&ic->ic_sta, vap, bssid); if (ni != NULL) { ni->ni_wdsvap = vap; IEEE80211_ADDR_COPY(ni->ni_bssid, bssid); /* * Inherit any manually configured settings. */ copy_bss(ni, vap->iv_bss); ieee80211_node_set_chan(ni, chan); /* NB: propagate ssid so available to WPA supplicant */ ni->ni_esslen = vap->iv_des_ssid[0].len; memcpy(ni->ni_essid, vap->iv_des_ssid[0].ssid, ni->ni_esslen); /* NB: no associd for peer */ /* * There are no management frames to use to * discover neighbor capabilities, so blindly * propagate the local configuration. */ if (vap->iv_flags & IEEE80211_F_WME) ni->ni_flags |= IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (vap->iv_flags & IEEE80211_F_FF) ni->ni_flags |= IEEE80211_NODE_FF; #endif if ((ic->ic_htcaps & IEEE80211_HTC_HT) && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { /* * Device is HT-capable and HT is enabled for * the vap; setup HT operation. On return * ni_chan will be adjusted to an HT channel. */ ieee80211_ht_wds_init(ni); } else { struct ieee80211_channel *c = ni->ni_chan; /* * Force a legacy channel to be used. */ c = ieee80211_find_channel(ic, c->ic_freq, c->ic_flags &~ IEEE80211_CHAN_HT); KASSERT(c != NULL, ("no legacy channel, %u/%x", ni->ni_chan->ic_freq, ni->ni_chan->ic_flags)); ni->ni_chan = c; } } return ni; } struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_node_locked_debug(struct ieee80211_node_table *nt, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line) #else ieee80211_find_node_locked(struct ieee80211_node_table *nt, const uint8_t macaddr[IEEE80211_ADDR_LEN]) #endif { struct ieee80211_node *ni; int hash; IEEE80211_NODE_LOCK_ASSERT(nt); hash = IEEE80211_NODE_HASH(nt->nt_ic, macaddr); LIST_FOREACH(ni, &nt->nt_hash[hash], ni_hash) { if (IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr)) { ieee80211_ref_node(ni); /* mark referenced */ #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); #endif return ni; } } return NULL; } struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_node_debug(struct ieee80211_node_table *nt, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line) #else ieee80211_find_node(struct ieee80211_node_table *nt, const uint8_t macaddr[IEEE80211_ADDR_LEN]) #endif { struct ieee80211_node *ni; IEEE80211_NODE_LOCK(nt); ni = ieee80211_find_node_locked(nt, macaddr); IEEE80211_NODE_UNLOCK(nt); return ni; } struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_vap_node_locked_debug(struct ieee80211_node_table *nt, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line) #else ieee80211_find_vap_node_locked(struct ieee80211_node_table *nt, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) #endif { struct ieee80211_node *ni; int hash; IEEE80211_NODE_LOCK_ASSERT(nt); hash = IEEE80211_NODE_HASH(nt->nt_ic, macaddr); LIST_FOREACH(ni, &nt->nt_hash[hash], ni_hash) { if (ni->ni_vap == vap && IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr)) { ieee80211_ref_node(ni); /* mark referenced */ #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); #endif return ni; } } return NULL; } struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_vap_node_debug(struct ieee80211_node_table *nt, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line) #else ieee80211_find_vap_node(struct ieee80211_node_table *nt, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) #endif { struct ieee80211_node *ni; IEEE80211_NODE_LOCK(nt); ni = ieee80211_find_vap_node_locked(nt, vap, macaddr); IEEE80211_NODE_UNLOCK(nt); return ni; } /* * Fake up a node; this handles node discovery in adhoc mode. * Note that for the driver's benefit we we treat this like * an association so the driver has an opportunity to setup * it's private state. */ struct ieee80211_node * ieee80211_fakeup_adhoc_node(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211_node *ni; IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE | IEEE80211_MSG_ASSOC, "%s: mac<%s>\n", __func__, ether_sprintf(macaddr)); ni = ieee80211_dup_bss(vap, macaddr); if (ni != NULL) { struct ieee80211com *ic = vap->iv_ic; /* XXX no rate negotiation; just dup */ ni->ni_rates = vap->iv_bss->ni_rates; if (ieee80211_iserp_rateset(&ni->ni_rates)) ni->ni_flags |= IEEE80211_NODE_ERP; if (vap->iv_opmode == IEEE80211_M_AHDEMO) { /* * In adhoc demo mode there are no management * frames to use to discover neighbor capabilities, * so blindly propagate the local configuration * so we can do interesting things (e.g. use * WME to disable ACK's). */ if (vap->iv_flags & IEEE80211_F_WME) ni->ni_flags |= IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (vap->iv_flags & IEEE80211_F_FF) ni->ni_flags |= IEEE80211_NODE_FF; #endif } ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); if (ic->ic_newassoc != NULL) ic->ic_newassoc(ni, 1); /* XXX not right for 802.1x/WPA */ ieee80211_node_authorize(ni); } return ni; } void ieee80211_init_neighbor(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const struct ieee80211_scanparams *sp) { int do_ht_setup = 0; ni->ni_esslen = sp->ssid[1]; memcpy(ni->ni_essid, sp->ssid + 2, sp->ssid[1]); IEEE80211_ADDR_COPY(ni->ni_bssid, wh->i_addr3); memcpy(ni->ni_tstamp.data, sp->tstamp, sizeof(ni->ni_tstamp)); ni->ni_intval = sp->bintval; ni->ni_capinfo = sp->capinfo; ni->ni_chan = ni->ni_ic->ic_curchan; ni->ni_fhdwell = sp->fhdwell; ni->ni_fhindex = sp->fhindex; ni->ni_erp = sp->erp; ni->ni_timoff = sp->timoff; #ifdef IEEE80211_SUPPORT_MESH if (ni->ni_vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_init_neighbor(ni, wh, sp); #endif if (ieee80211_ies_init(&ni->ni_ies, sp->ies, sp->ies_len)) { ieee80211_ies_expand(&ni->ni_ies); if (ni->ni_ies.wme_ie != NULL) ni->ni_flags |= IEEE80211_NODE_QOS; else ni->ni_flags &= ~IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (ni->ni_ies.ath_ie != NULL) ieee80211_parse_ath(ni, ni->ni_ies.ath_ie); #endif if (ni->ni_ies.htcap_ie != NULL) ieee80211_parse_htcap(ni, ni->ni_ies.htcap_ie); if (ni->ni_ies.htinfo_ie != NULL) ieee80211_parse_htinfo(ni, ni->ni_ies.htinfo_ie); if ((ni->ni_ies.htcap_ie != NULL) && (ni->ni_ies.htinfo_ie != NULL) && (ni->ni_vap->iv_flags_ht & IEEE80211_FHT_HT)) { do_ht_setup = 1; } } /* NB: must be after ni_chan is setup */ ieee80211_setup_rates(ni, sp->rates, sp->xrates, IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); /* * If the neighbor is HT compatible, flip that on. */ if (do_ht_setup) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: doing HT setup\n", __func__); ieee80211_ht_node_init(ni); ieee80211_ht_updateparams(ni, ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); ieee80211_setup_htrates(ni, ni->ni_ies.htcap_ie, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, ni->ni_ies.htinfo_ie); ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); } } /* * Do node discovery in adhoc mode on receipt of a beacon * or probe response frame. Note that for the driver's * benefit we we treat this like an association so the * driver has an opportunity to setup it's private state. */ struct ieee80211_node * ieee80211_add_neighbor(struct ieee80211vap *vap, const struct ieee80211_frame *wh, const struct ieee80211_scanparams *sp) { struct ieee80211_node *ni; IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s: mac<%s>\n", __func__, ether_sprintf(wh->i_addr2)); ni = ieee80211_dup_bss(vap, wh->i_addr2);/* XXX alloc_node? */ if (ni != NULL) { struct ieee80211com *ic = vap->iv_ic; ieee80211_init_neighbor(ni, wh, sp); if (ieee80211_iserp_rateset(&ni->ni_rates)) ni->ni_flags |= IEEE80211_NODE_ERP; ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); if (ic->ic_newassoc != NULL) ic->ic_newassoc(ni, 1); /* XXX not right for 802.1x/WPA */ ieee80211_node_authorize(ni); } return ni; } #define IS_PROBEREQ(wh) \ ((wh->i_fc[0] & (IEEE80211_FC0_TYPE_MASK|IEEE80211_FC0_SUBTYPE_MASK)) \ == (IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_REQ)) #define IS_BCAST_PROBEREQ(wh) \ (IS_PROBEREQ(wh) && IEEE80211_IS_MULTICAST( \ ((const struct ieee80211_frame *)(wh))->i_addr3)) static __inline struct ieee80211_node * _find_rxnode(struct ieee80211_node_table *nt, const struct ieee80211_frame_min *wh) { if (IS_BCAST_PROBEREQ(wh)) return NULL; /* spam bcast probe req to all vap's */ return ieee80211_find_node_locked(nt, wh->i_addr2); } /* * Locate the node for sender, track state, and then pass the * (referenced) node up to the 802.11 layer for its use. Note * we can return NULL if the sender is not in the table. */ struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_rxnode_debug(struct ieee80211com *ic, const struct ieee80211_frame_min *wh, const char *func, int line) #else ieee80211_find_rxnode(struct ieee80211com *ic, const struct ieee80211_frame_min *wh) #endif { struct ieee80211_node_table *nt; struct ieee80211_node *ni; nt = &ic->ic_sta; IEEE80211_NODE_LOCK(nt); ni = _find_rxnode(nt, wh); IEEE80211_NODE_UNLOCK(nt); return ni; } /* * Like ieee80211_find_rxnode but use the supplied h/w * key index as a hint to locate the node in the key * mapping table. If an entry is present at the key * index we return it; otherwise do a normal lookup and * update the mapping table if the station has a unicast * key assigned to it. */ struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_rxnode_withkey_debug(struct ieee80211com *ic, const struct ieee80211_frame_min *wh, ieee80211_keyix keyix, const char *func, int line) #else ieee80211_find_rxnode_withkey(struct ieee80211com *ic, const struct ieee80211_frame_min *wh, ieee80211_keyix keyix) #endif { struct ieee80211_node_table *nt; struct ieee80211_node *ni; nt = &ic->ic_sta; IEEE80211_NODE_LOCK(nt); if (nt->nt_keyixmap != NULL && keyix < nt->nt_keyixmax) ni = nt->nt_keyixmap[keyix]; else ni = NULL; if (ni == NULL) { ni = _find_rxnode(nt, wh); if (ni != NULL && nt->nt_keyixmap != NULL) { /* * If the station has a unicast key cache slot * assigned update the key->node mapping table. */ keyix = ni->ni_ucastkey.wk_rxkeyix; /* XXX can keyixmap[keyix] != NULL? */ if (keyix < nt->nt_keyixmax && nt->nt_keyixmap[keyix] == NULL) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: add key map entry %p<%s> refcnt %d\n", __func__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); nt->nt_keyixmap[keyix] = ieee80211_ref_node(ni); } } } else { if (IS_BCAST_PROBEREQ(wh)) ni = NULL; /* spam bcast probe req to all vap's */ else ieee80211_ref_node(ni); } IEEE80211_NODE_UNLOCK(nt); return ni; } #undef IS_BCAST_PROBEREQ #undef IS_PROBEREQ /* * Return a reference to the appropriate node for sending * a data frame. This handles node discovery in adhoc networks. */ struct ieee80211_node * #ifdef IEEE80211_DEBUG_REFCNT ieee80211_find_txnode_debug(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line) #else ieee80211_find_txnode(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) #endif { struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta; struct ieee80211_node *ni; /* * The destination address should be in the node table * unless this is a multicast/broadcast frame. We can * also optimize station mode operation, all frames go * to the bss node. */ /* XXX can't hold lock across dup_bss 'cuz of recursive locking */ IEEE80211_NODE_LOCK(nt); if (vap->iv_opmode == IEEE80211_M_STA || vap->iv_opmode == IEEE80211_M_WDS || IEEE80211_IS_MULTICAST(macaddr)) ni = ieee80211_ref_node(vap->iv_bss); else ni = ieee80211_find_node_locked(nt, macaddr); IEEE80211_NODE_UNLOCK(nt); if (ni == NULL) { if (vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO) { /* * In adhoc mode cons up a node for the destination. * Note that we need an additional reference for the * caller to be consistent with * ieee80211_find_node_locked. */ ni = ieee80211_fakeup_adhoc_node(vap, macaddr); if (ni != NULL) (void) ieee80211_ref_node(ni); } else { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_OUTPUT, macaddr, "no node, discard frame (%s)", __func__); vap->iv_stats.is_tx_nonode++; } } return ni; } static void _ieee80211_free_node(struct ieee80211_node *ni) { struct ieee80211_node_table *nt = ni->ni_table; /* * NB: careful about referencing the vap as it may be * gone if the last reference was held by a driver. * We know the com will always be present so it's safe * to use ni_ic below to reclaim resources. */ #if 0 IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s %p<%s> in %s table\n", __func__, ni, ether_sprintf(ni->ni_macaddr), nt != NULL ? nt->nt_name : ""); #endif if (ni->ni_associd != 0) { struct ieee80211vap *vap = ni->ni_vap; if (vap->iv_aid_bitmap != NULL) IEEE80211_AID_CLR(vap, ni->ni_associd); } if (nt != NULL) { TAILQ_REMOVE(&nt->nt_node, ni, ni_list); LIST_REMOVE(ni, ni_hash); } ni->ni_ic->ic_node_free(ni); } /* * Clear any entry in the unicast key mapping table. */ static int node_clear_keyixmap(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { ieee80211_keyix keyix; keyix = ni->ni_ucastkey.wk_rxkeyix; if (nt->nt_keyixmap != NULL && keyix < nt->nt_keyixmax && nt->nt_keyixmap[keyix] == ni) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: %p<%s> clear key map entry %u\n", __func__, ni, ether_sprintf(ni->ni_macaddr), keyix); nt->nt_keyixmap[keyix] = NULL; ieee80211_node_decref(ni); return 1; } return 0; } void #ifdef IEEE80211_DEBUG_REFCNT ieee80211_free_node_debug(struct ieee80211_node *ni, const char *func, int line) #else ieee80211_free_node(struct ieee80211_node *ni) #endif { struct ieee80211_node_table *nt = ni->ni_table; #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)-1); #endif if (nt != NULL) { IEEE80211_NODE_LOCK(nt); if (ieee80211_node_dectestref(ni)) { /* * Last reference, reclaim state. */ _ieee80211_free_node(ni); } else if (ieee80211_node_refcnt(ni) == 1) if (node_clear_keyixmap(nt, ni)) _ieee80211_free_node(ni); IEEE80211_NODE_UNLOCK(nt); } else { if (ieee80211_node_dectestref(ni)) _ieee80211_free_node(ni); } } /* * Reclaim a unicast key and clear any key cache state. */ int ieee80211_node_delucastkey(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_node_table *nt = &ic->ic_sta; struct ieee80211_node *nikey; ieee80211_keyix keyix; int isowned, status; /* * NB: We must beware of LOR here; deleting the key * can cause the crypto layer to block traffic updates * which can generate a LOR against the node table lock; * grab it here and stash the key index for our use below. * * Must also beware of recursion on the node table lock. * When called from node_cleanup we may already have * the node table lock held. Unfortunately there's no * way to separate out this path so we must do this * conditionally. */ isowned = IEEE80211_NODE_IS_LOCKED(nt); if (!isowned) IEEE80211_NODE_LOCK(nt); nikey = NULL; status = 1; /* NB: success */ if (ni->ni_ucastkey.wk_keyix != IEEE80211_KEYIX_NONE) { keyix = ni->ni_ucastkey.wk_rxkeyix; status = ieee80211_crypto_delkey(ni->ni_vap, &ni->ni_ucastkey); if (nt->nt_keyixmap != NULL && keyix < nt->nt_keyixmax) { nikey = nt->nt_keyixmap[keyix]; nt->nt_keyixmap[keyix] = NULL; } } if (!isowned) IEEE80211_NODE_UNLOCK(nt); if (nikey != NULL) { KASSERT(nikey == ni, ("key map out of sync, ni %p nikey %p", ni, nikey)); IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: delete key map entry %p<%s> refcnt %d\n", __func__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)-1); ieee80211_free_node(ni); } return status; } /* * Reclaim a node. If this is the last reference count then * do the normal free work. Otherwise remove it from the node * table and mark it gone by clearing the back-reference. */ static void node_reclaim(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { IEEE80211_NODE_LOCK_ASSERT(nt); IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: remove %p<%s> from %s table, refcnt %d\n", __func__, ni, ether_sprintf(ni->ni_macaddr), nt->nt_name, ieee80211_node_refcnt(ni)-1); /* * Clear any entry in the unicast key mapping table. * We need to do it here so rx lookups don't find it * in the mapping table even if it's not in the hash * table. We cannot depend on the mapping table entry * being cleared because the node may not be free'd. */ (void)node_clear_keyixmap(nt, ni); if (!ieee80211_node_dectestref(ni)) { /* * Other references are present, just remove the * node from the table so it cannot be found. When * the references are dropped storage will be * reclaimed. */ TAILQ_REMOVE(&nt->nt_node, ni, ni_list); LIST_REMOVE(ni, ni_hash); ni->ni_table = NULL; /* clear reference */ } else _ieee80211_free_node(ni); } /* * Node table support. */ static void ieee80211_node_table_init(struct ieee80211com *ic, struct ieee80211_node_table *nt, const char *name, int inact, int keyixmax) { nt->nt_ic = ic; IEEE80211_NODE_LOCK_INIT(nt, ic->ic_name); IEEE80211_NODE_ITERATE_LOCK_INIT(nt, ic->ic_name); TAILQ_INIT(&nt->nt_node); nt->nt_name = name; nt->nt_scangen = 1; nt->nt_inact_init = inact; nt->nt_keyixmax = keyixmax; if (nt->nt_keyixmax > 0) { nt->nt_keyixmap = (struct ieee80211_node **) IEEE80211_MALLOC( keyixmax * sizeof(struct ieee80211_node *), M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (nt->nt_keyixmap == NULL) ic_printf(ic, "Cannot allocate key index map with %u entries\n", keyixmax); } else nt->nt_keyixmap = NULL; } static void ieee80211_node_table_reset(struct ieee80211_node_table *nt, struct ieee80211vap *match) { struct ieee80211_node *ni, *next; IEEE80211_NODE_LOCK(nt); TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, next) { if (match != NULL && ni->ni_vap != match) continue; /* XXX can this happen? if so need's work */ if (ni->ni_associd != 0) { struct ieee80211vap *vap = ni->ni_vap; if (vap->iv_auth->ia_node_leave != NULL) vap->iv_auth->ia_node_leave(ni); if (vap->iv_aid_bitmap != NULL) IEEE80211_AID_CLR(vap, ni->ni_associd); } ni->ni_wdsvap = NULL; /* clear reference */ node_reclaim(nt, ni); } if (match != NULL && match->iv_opmode == IEEE80211_M_WDS) { /* * Make a separate pass to clear references to this vap * held by DWDS entries. They will not be matched above * because ni_vap will point to the ap vap but we still * need to clear ni_wdsvap when the WDS vap is destroyed * and/or reset. */ TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, next) if (ni->ni_wdsvap == match) ni->ni_wdsvap = NULL; } IEEE80211_NODE_UNLOCK(nt); } static void ieee80211_node_table_cleanup(struct ieee80211_node_table *nt) { ieee80211_node_table_reset(nt, NULL); if (nt->nt_keyixmap != NULL) { #ifdef DIAGNOSTIC /* XXX verify all entries are NULL */ int i; for (i = 0; i < nt->nt_keyixmax; i++) if (nt->nt_keyixmap[i] != NULL) printf("%s: %s[%u] still active\n", __func__, nt->nt_name, i); #endif IEEE80211_FREE(nt->nt_keyixmap, M_80211_NODE); nt->nt_keyixmap = NULL; } IEEE80211_NODE_ITERATE_LOCK_DESTROY(nt); IEEE80211_NODE_LOCK_DESTROY(nt); } /* * Timeout inactive stations and do related housekeeping. * Note that we cannot hold the node lock while sending a * frame as this would lead to a LOR. Instead we use a * generation number to mark nodes that we've scanned and * drop the lock and restart a scan if we have to time out * a node. Since we are single-threaded by virtue of * controlling the inactivity timer we can be sure this will * process each node only once. */ static void ieee80211_timeout_stations(struct ieee80211com *ic) { struct ieee80211_node_table *nt = &ic->ic_sta; struct ieee80211vap *vap; struct ieee80211_node *ni; int gen = 0; IEEE80211_NODE_ITERATE_LOCK(nt); gen = ++nt->nt_scangen; restart: IEEE80211_NODE_LOCK(nt); TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { if (ni->ni_scangen == gen) /* previously handled */ continue; ni->ni_scangen = gen; /* * Ignore entries for which have yet to receive an * authentication frame. These are transient and * will be reclaimed when the last reference to them * goes away (when frame xmits complete). */ vap = ni->ni_vap; /* * Only process stations when in RUN state. This * insures, for example, that we don't timeout an * inactive station during CAC. Note that CSA state * is actually handled in ieee80211_node_timeout as * it applies to more than timeout processing. */ if (vap->iv_state != IEEE80211_S_RUN) continue; /* XXX can vap be NULL? */ if ((vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_STA) && (ni->ni_flags & IEEE80211_NODE_AREF) == 0) continue; /* * Free fragment if not needed anymore * (last fragment older than 1s). * XXX doesn't belong here, move to node_age */ if (ni->ni_rxfrag[0] != NULL && ticks > ni->ni_rxfragstamp + hz) { m_freem(ni->ni_rxfrag[0]); ni->ni_rxfrag[0] = NULL; } if (ni->ni_inact > 0) { ni->ni_inact--; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact %u inact_reload %u nrates %u", __func__, ni->ni_inact, ni->ni_inact_reload, ni->ni_rates.rs_nrates); } /* * Special case ourself; we may be idle for extended periods * of time and regardless reclaiming our state is wrong. * XXX run ic_node_age */ if (ni == vap->iv_bss) continue; if (ni->ni_associd != 0 || (vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO)) { /* * Age/drain resources held by the station. */ ic->ic_node_age(ni); /* * Probe the station before time it out. We * send a null data frame which may not be * universally supported by drivers (need it * for ps-poll support so it should be...). * * XXX don't probe the station unless we've * received a frame from them (and have * some idea of the rates they are capable * of); this will get fixed more properly * soon with better handling of the rate set. */ if ((vap->iv_flags_ext & IEEE80211_FEXT_INACT) && (0 < ni->ni_inact && ni->ni_inact <= vap->iv_inact_probe) && ni->ni_rates.rs_nrates != 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_INACT | IEEE80211_MSG_NODE, ni, "%s", "probe station due to inactivity"); /* * Grab a reference before unlocking the table * so the node cannot be reclaimed before we * send the frame. ieee80211_send_nulldata * understands we've done this and reclaims the * ref for us as needed. */ ieee80211_ref_node(ni); IEEE80211_NODE_UNLOCK(nt); ieee80211_send_nulldata(ni); /* XXX stat? */ goto restart; } } if ((vap->iv_flags_ext & IEEE80211_FEXT_INACT) && ni->ni_inact <= 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_INACT | IEEE80211_MSG_NODE, ni, "station timed out due to inactivity " "(refcnt %u)", ieee80211_node_refcnt(ni)); /* * Send a deauthenticate frame and drop the station. * This is somewhat complicated due to reference counts * and locking. At this point a station will typically * have a reference count of 1. ieee80211_node_leave * will do a "free" of the node which will drop the * reference count. But in the meantime a reference * wil be held by the deauth frame. The actual reclaim * of the node will happen either after the tx is * completed or by ieee80211_node_leave. * * Separately we must drop the node lock before sending * in case the driver takes a lock, as this can result * in a LOR between the node lock and the driver lock. */ ieee80211_ref_node(ni); IEEE80211_NODE_UNLOCK(nt); if (ni->ni_associd != 0) { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_AUTH_EXPIRE); } ieee80211_node_leave(ni); ieee80211_free_node(ni); vap->iv_stats.is_node_timeout++; goto restart; } } IEEE80211_NODE_UNLOCK(nt); IEEE80211_NODE_ITERATE_UNLOCK(nt); } /* * Aggressively reclaim resources. This should be used * only in a critical situation to reclaim mbuf resources. */ void ieee80211_drain(struct ieee80211com *ic) { struct ieee80211_node_table *nt = &ic->ic_sta; struct ieee80211vap *vap; struct ieee80211_node *ni; IEEE80211_NODE_LOCK(nt); TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { /* * Ignore entries for which have yet to receive an * authentication frame. These are transient and * will be reclaimed when the last reference to them * goes away (when frame xmits complete). */ vap = ni->ni_vap; /* * Only process stations when in RUN state. This * insures, for example, that we don't timeout an * inactive station during CAC. Note that CSA state * is actually handled in ieee80211_node_timeout as * it applies to more than timeout processing. */ if (vap->iv_state != IEEE80211_S_RUN) continue; /* XXX can vap be NULL? */ if ((vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_STA) && (ni->ni_flags & IEEE80211_NODE_AREF) == 0) continue; /* * Free fragments. * XXX doesn't belong here, move to node_drain */ if (ni->ni_rxfrag[0] != NULL) { m_freem(ni->ni_rxfrag[0]); ni->ni_rxfrag[0] = NULL; } /* * Drain resources held by the station. */ ic->ic_node_drain(ni); } IEEE80211_NODE_UNLOCK(nt); } /* * Per-ieee80211com inactivity timer callback. */ void ieee80211_node_timeout(void *arg) { struct ieee80211com *ic = arg; /* * Defer timeout processing if a channel switch is pending. * We typically need to be mute so not doing things that * might generate frames is good to handle in one place. * Supressing the station timeout processing may extend the * lifetime of inactive stations (by not decrementing their * idle counters) but this should be ok unless the CSA is * active for an unusually long time. */ if ((ic->ic_flags & IEEE80211_F_CSAPENDING) == 0) { ieee80211_scan_timeout(ic); ieee80211_timeout_stations(ic); ieee80211_ageq_age(&ic->ic_stageq, IEEE80211_INACT_WAIT); IEEE80211_LOCK(ic); ieee80211_erp_timeout(ic); ieee80211_ht_timeout(ic); IEEE80211_UNLOCK(ic); } callout_reset(&ic->ic_inact, IEEE80211_INACT_WAIT*hz, ieee80211_node_timeout, ic); } /* * Iterate over the node table and return an array of ref'ed nodes. * * This is separated out from calling the actual node function so that * no LORs will occur. * * If there are too many nodes (ie, the number of nodes doesn't fit * within 'max_aid' entries) then the node references will be freed * and an error will be returned. * * The responsibility of allocating and freeing "ni_arr" is up to * the caller. */ int ieee80211_iterate_nt(struct ieee80211_node_table *nt, struct ieee80211_node **ni_arr, uint16_t max_aid) { u_int gen; int i, j, ret; struct ieee80211_node *ni; IEEE80211_NODE_ITERATE_LOCK(nt); IEEE80211_NODE_LOCK(nt); gen = ++nt->nt_scangen; i = ret = 0; /* * We simply assume here that since the node * scan generation doesn't change (as * we are holding both the node table and * node table iteration locks), we can simply * assign it to the node here. */ TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { if (i >= max_aid) { ret = E2BIG; ic_printf(nt->nt_ic, "Node array overflow: max=%u", max_aid); break; } ni_arr[i] = ieee80211_ref_node(ni); ni_arr[i]->ni_scangen = gen; i++; } /* * It's safe to unlock here. * * If we're successful, the list is returned. * If we're unsuccessful, the list is ignored * and we remove our references. * * This avoids any potential LOR with * ieee80211_free_node(). */ IEEE80211_NODE_UNLOCK(nt); IEEE80211_NODE_ITERATE_UNLOCK(nt); /* * If ret is non-zero, we hit some kind of error. * Rather than walking some nodes, we'll walk none * of them. */ if (ret) { for (j = 0; j < i; j++) { /* ieee80211_free_node() locks by itself */ ieee80211_free_node(ni_arr[j]); } } return (ret); } /* * Just a wrapper, so we don't have to change every ieee80211_iterate_nodes() * reference in the source. * * Note that this fetches 'max_aid' from the first VAP, rather than finding * the largest max_aid from all VAPs. */ void ieee80211_iterate_nodes(struct ieee80211_node_table *nt, ieee80211_iter_func *f, void *arg) { struct ieee80211_node **ni_arr; size_t size; int i; uint16_t max_aid; struct ieee80211vap *vap; /* Overdoing it default */ max_aid = IEEE80211_AID_MAX; /* Handle the case of there being no vaps just yet */ vap = TAILQ_FIRST(&nt->nt_ic->ic_vaps); if (vap != NULL) max_aid = vap->iv_max_aid; size = max_aid * sizeof(struct ieee80211_node *); ni_arr = (struct ieee80211_node **) IEEE80211_MALLOC(size, M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (ni_arr == NULL) return; /* * If this fails, the node table won't have any * valid entries - ieee80211_iterate_nt() frees * the references to them. So don't try walking * the table; just skip to the end and free the * temporary memory. */ if (ieee80211_iterate_nt(nt, ni_arr, max_aid) != 0) goto done; for (i = 0; i < max_aid; i++) { if (ni_arr[i] == NULL) /* end of the list */ break; (*f)(arg, ni_arr[i]); /* ieee80211_free_node() locks by itself */ ieee80211_free_node(ni_arr[i]); } done: IEEE80211_FREE(ni_arr, M_80211_NODE); } void ieee80211_dump_node(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { printf("0x%p: mac %s refcnt %d\n", ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); printf("\tscangen %u authmode %u flags 0x%x\n", ni->ni_scangen, ni->ni_authmode, ni->ni_flags); printf("\tassocid 0x%x txpower %u vlan %u\n", ni->ni_associd, ni->ni_txpower, ni->ni_vlan); printf("\ttxseq %u rxseq %u fragno %u rxfragstamp %u\n", ni->ni_txseqs[IEEE80211_NONQOS_TID], ni->ni_rxseqs[IEEE80211_NONQOS_TID] >> IEEE80211_SEQ_SEQ_SHIFT, ni->ni_rxseqs[IEEE80211_NONQOS_TID] & IEEE80211_SEQ_FRAG_MASK, ni->ni_rxfragstamp); printf("\trssi %d noise %d intval %u capinfo 0x%x\n", node_getrssi(ni), ni->ni_noise, ni->ni_intval, ni->ni_capinfo); printf("\tbssid %s essid \"%.*s\" channel %u:0x%x\n", ether_sprintf(ni->ni_bssid), ni->ni_esslen, ni->ni_essid, ni->ni_chan->ic_freq, ni->ni_chan->ic_flags); printf("\tinact %u inact_reload %u txrate %u\n", ni->ni_inact, ni->ni_inact_reload, ni->ni_txrate); printf("\thtcap %x htparam %x htctlchan %u ht2ndchan %u\n", ni->ni_htcap, ni->ni_htparam, ni->ni_htctlchan, ni->ni_ht2ndchan); printf("\thtopmode %x htstbc %x chw %u\n", ni->ni_htopmode, ni->ni_htstbc, ni->ni_chw); } void ieee80211_dump_nodes(struct ieee80211_node_table *nt) { ieee80211_iterate_nodes(nt, (ieee80211_iter_func *) ieee80211_dump_node, nt); } static void ieee80211_notify_erp_locked(struct ieee80211com *ic) { struct ieee80211vap *vap; IEEE80211_LOCK_ASSERT(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_opmode == IEEE80211_M_HOSTAP) ieee80211_beacon_notify(vap, IEEE80211_BEACON_ERP); } void ieee80211_notify_erp(struct ieee80211com *ic) { IEEE80211_LOCK(ic); ieee80211_notify_erp_locked(ic); IEEE80211_UNLOCK(ic); } /* * Handle a station joining an 11g network. */ static void ieee80211_node_join_11g(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; IEEE80211_LOCK_ASSERT(ic); /* * Station isn't capable of short slot time. Bump * the count of long slot time stations and disable * use of short slot time. Note that the actual switch * over to long slot time use may not occur until the * next beacon transmission (per sec. 7.3.1.4 of 11g). */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) == 0) { ic->ic_longslotsta++; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "station needs long slot time, count %d", ic->ic_longslotsta); /* XXX vap's w/ conflicting needs won't work */ if (!IEEE80211_IS_CHAN_108G(ic->ic_bsschan)) { /* * Don't force slot time when switched to turbo * mode as non-ERP stations won't be present; this * need only be done when on the normal G channel. */ ieee80211_set_shortslottime(ic, 0); } } /* * If the new station is not an ERP station * then bump the counter and enable protection * if configured. */ if (!ieee80211_iserp_rateset(&ni->ni_rates)) { ic->ic_nonerpsta++; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "station is !ERP, %d non-ERP stations associated", ic->ic_nonerpsta); /* * If station does not support short preamble * then we must enable use of Barker preamble. */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE) == 0) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "%s", "station needs long preamble"); ic->ic_flags |= IEEE80211_F_USEBARKER; ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE; } /* * If protection is configured and this is the first * indication we should use protection, enable it. */ if (ic->ic_protmode != IEEE80211_PROT_NONE && ic->ic_nonerpsta == 1 && (ic->ic_flags_ext & IEEE80211_FEXT_NONERP_PR) == 0) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: enable use of protection\n", __func__); ic->ic_flags |= IEEE80211_F_USEPROT; ieee80211_notify_erp_locked(ic); } } else ni->ni_flags |= IEEE80211_NODE_ERP; } void ieee80211_node_join(struct ieee80211_node *ni, int resp) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; int newassoc; if (ni->ni_associd == 0) { uint16_t aid; KASSERT(vap->iv_aid_bitmap != NULL, ("no aid bitmap")); /* * It would be good to search the bitmap * more efficiently, but this will do for now. */ for (aid = 1; aid < vap->iv_max_aid; aid++) { if (!IEEE80211_AID_ISSET(vap, aid)) break; } if (aid >= vap->iv_max_aid) { IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_TOOMANY); ieee80211_node_leave(ni); return; } ni->ni_associd = aid | 0xc000; ni->ni_jointime = time_uptime; IEEE80211_LOCK(ic); IEEE80211_AID_SET(vap, ni->ni_associd); vap->iv_sta_assoc++; ic->ic_sta_assoc++; if (IEEE80211_IS_CHAN_HT(ic->ic_bsschan)) ieee80211_ht_node_join(ni); if (IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && IEEE80211_IS_CHAN_FULL(ic->ic_bsschan)) ieee80211_node_join_11g(ni); IEEE80211_UNLOCK(ic); newassoc = 1; } else newassoc = 0; IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, ni, "station associated at aid %d: %s preamble, %s slot time%s%s%s%s%s%s%s%s", IEEE80211_NODE_AID(ni), ic->ic_flags & IEEE80211_F_SHPREAMBLE ? "short" : "long", ic->ic_flags & IEEE80211_F_SHSLOT ? "short" : "long", ic->ic_flags & IEEE80211_F_USEPROT ? ", protection" : "", ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "", ni->ni_flags & IEEE80211_NODE_HT ? (ni->ni_chw == 40 ? ", HT40" : ", HT20") : "", ni->ni_flags & IEEE80211_NODE_AMPDU ? " (+AMPDU)" : "", ni->ni_flags & IEEE80211_NODE_MIMO_RTS ? " (+SMPS-DYN)" : ni->ni_flags & IEEE80211_NODE_MIMO_PS ? " (+SMPS)" : "", ni->ni_flags & IEEE80211_NODE_RIFS ? " (+RIFS)" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF) ? ", fast-frames" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_TURBOP) ? ", turbo" : "" ); ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); /* give driver a chance to setup state like ni_txrate */ if (ic->ic_newassoc != NULL) ic->ic_newassoc(ni, newassoc); IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_SUCCESS); /* tell the authenticator about new station */ if (vap->iv_auth->ia_node_join != NULL) vap->iv_auth->ia_node_join(ni); ieee80211_notify_node_join(ni, resp == IEEE80211_FC0_SUBTYPE_ASSOC_RESP); } static void disable_protection(struct ieee80211com *ic) { KASSERT(ic->ic_nonerpsta == 0 && (ic->ic_flags_ext & IEEE80211_FEXT_NONERP_PR) == 0, ("%d non ERP stations, flags 0x%x", ic->ic_nonerpsta, ic->ic_flags_ext)); ic->ic_flags &= ~IEEE80211_F_USEPROT; /* XXX verify mode? */ if (ic->ic_caps & IEEE80211_C_SHPREAMBLE) { ic->ic_flags |= IEEE80211_F_SHPREAMBLE; ic->ic_flags &= ~IEEE80211_F_USEBARKER; } ieee80211_notify_erp_locked(ic); } /* * Handle a station leaving an 11g network. */ static void ieee80211_node_leave_11g(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; IEEE80211_LOCK_ASSERT(ic); KASSERT(IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan), ("not in 11g, bss %u:0x%x", ic->ic_bsschan->ic_freq, ic->ic_bsschan->ic_flags)); /* * If a long slot station do the slot time bookkeeping. */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) == 0) { KASSERT(ic->ic_longslotsta > 0, ("bogus long slot station count %d", ic->ic_longslotsta)); ic->ic_longslotsta--; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "long slot time station leaves, count now %d", ic->ic_longslotsta); if (ic->ic_longslotsta == 0) { /* * Re-enable use of short slot time if supported * and not operating in IBSS mode (per spec). */ if ((ic->ic_caps & IEEE80211_C_SHSLOT) && ic->ic_opmode != IEEE80211_M_IBSS) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: re-enable use of short slot time\n", __func__); ieee80211_set_shortslottime(ic, 1); } } } /* * If a non-ERP station do the protection-related bookkeeping. */ if ((ni->ni_flags & IEEE80211_NODE_ERP) == 0) { KASSERT(ic->ic_nonerpsta > 0, ("bogus non-ERP station count %d", ic->ic_nonerpsta)); ic->ic_nonerpsta--; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "non-ERP station leaves, count now %d%s", ic->ic_nonerpsta, (ic->ic_flags_ext & IEEE80211_FEXT_NONERP_PR) ? " (non-ERP sta present)" : ""); if (ic->ic_nonerpsta == 0 && (ic->ic_flags_ext & IEEE80211_FEXT_NONERP_PR) == 0) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: disable use of protection\n", __func__); disable_protection(ic); } } } /* * Time out presence of an overlapping bss with non-ERP * stations. When operating in hostap mode we listen for * beacons from other stations and if we identify a non-ERP * station is present we enable protection. To identify * when all non-ERP stations are gone we time out this * condition. */ static void ieee80211_erp_timeout(struct ieee80211com *ic) { IEEE80211_LOCK_ASSERT(ic); if ((ic->ic_flags_ext & IEEE80211_FEXT_NONERP_PR) && ieee80211_time_after(ticks, ic->ic_lastnonerp + IEEE80211_NONERP_PRESENT_AGE)) { #if 0 IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, "%s", "age out non-ERP sta present on channel"); #endif ic->ic_flags_ext &= ~IEEE80211_FEXT_NONERP_PR; if (ic->ic_nonerpsta == 0) disable_protection(ic); } } /* * Handle bookkeeping for station deauthentication/disassociation * when operating as an ap. */ void ieee80211_node_leave(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_node_table *nt = ni->ni_table; IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, ni, "station with aid %d leaves", IEEE80211_NODE_AID(ni)); KASSERT(vap->iv_opmode != IEEE80211_M_STA, ("unexpected operating mode %u", vap->iv_opmode)); /* * If node wasn't previously associated all * we need to do is reclaim the reference. */ /* XXX ibss mode bypasses 11g and notification */ if (ni->ni_associd == 0) goto done; /* * Tell the authenticator the station is leaving. * Note that we must do this before yanking the * association id as the authenticator uses the * associd to locate it's state block. */ if (vap->iv_auth->ia_node_leave != NULL) vap->iv_auth->ia_node_leave(ni); IEEE80211_LOCK(ic); IEEE80211_AID_CLR(vap, ni->ni_associd); vap->iv_sta_assoc--; ic->ic_sta_assoc--; if (IEEE80211_IS_CHAN_HT(ic->ic_bsschan)) ieee80211_ht_node_leave(ni); if (IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && IEEE80211_IS_CHAN_FULL(ic->ic_bsschan)) ieee80211_node_leave_11g(ni); IEEE80211_UNLOCK(ic); /* * Cleanup station state. In particular clear various * state that might otherwise be reused if the node * is reused before the reference count goes to zero * (and memory is reclaimed). */ ieee80211_sta_leave(ni); done: /* * Remove the node from any table it's recorded in and * drop the caller's reference. Removal from the table * is important to insure the node is not reprocessed * for inactivity. */ if (nt != NULL) { IEEE80211_NODE_LOCK(nt); node_reclaim(nt, ni); IEEE80211_NODE_UNLOCK(nt); } else ieee80211_free_node(ni); } struct rssiinfo { struct ieee80211vap *vap; int rssi_samples; uint32_t rssi_total; }; static void get_hostap_rssi(void *arg, struct ieee80211_node *ni) { struct rssiinfo *info = arg; struct ieee80211vap *vap = ni->ni_vap; int8_t rssi; if (info->vap != vap) return; /* only associated stations */ if (ni->ni_associd == 0) return; rssi = vap->iv_ic->ic_node_getrssi(ni); if (rssi != 0) { info->rssi_samples++; info->rssi_total += rssi; } } static void get_adhoc_rssi(void *arg, struct ieee80211_node *ni) { struct rssiinfo *info = arg; struct ieee80211vap *vap = ni->ni_vap; int8_t rssi; if (info->vap != vap) return; /* only neighbors */ /* XXX check bssid */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) return; rssi = vap->iv_ic->ic_node_getrssi(ni); if (rssi != 0) { info->rssi_samples++; info->rssi_total += rssi; } } #ifdef IEEE80211_SUPPORT_MESH static void get_mesh_rssi(void *arg, struct ieee80211_node *ni) { struct rssiinfo *info = arg; struct ieee80211vap *vap = ni->ni_vap; int8_t rssi; if (info->vap != vap) return; /* only neighbors that peered successfully */ if (ni->ni_mlstate != IEEE80211_NODE_MESH_ESTABLISHED) return; rssi = vap->iv_ic->ic_node_getrssi(ni); if (rssi != 0) { info->rssi_samples++; info->rssi_total += rssi; } } #endif /* IEEE80211_SUPPORT_MESH */ int8_t ieee80211_getrssi(struct ieee80211vap *vap) { #define NZ(x) ((x) == 0 ? 1 : (x)) struct ieee80211com *ic = vap->iv_ic; struct rssiinfo info; info.rssi_total = 0; info.rssi_samples = 0; info.vap = vap; switch (vap->iv_opmode) { case IEEE80211_M_IBSS: /* average of all ibss neighbors */ case IEEE80211_M_AHDEMO: /* average of all neighbors */ ieee80211_iterate_nodes(&ic->ic_sta, get_adhoc_rssi, &info); break; case IEEE80211_M_HOSTAP: /* average of all associated stations */ ieee80211_iterate_nodes(&ic->ic_sta, get_hostap_rssi, &info); break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_M_MBSS: /* average of all mesh neighbors */ ieee80211_iterate_nodes(&ic->ic_sta, get_mesh_rssi, &info); break; #endif case IEEE80211_M_MONITOR: /* XXX */ case IEEE80211_M_STA: /* use stats from associated ap */ default: if (vap->iv_bss != NULL) info.rssi_total = ic->ic_node_getrssi(vap->iv_bss); info.rssi_samples = 1; break; } return info.rssi_total / NZ(info.rssi_samples); #undef NZ } void ieee80211_getsignal(struct ieee80211vap *vap, int8_t *rssi, int8_t *noise) { if (vap->iv_bss == NULL) /* NB: shouldn't happen */ return; vap->iv_ic->ic_node_getsignal(vap->iv_bss, rssi, noise); /* for non-station mode return avg'd rssi accounting */ if (vap->iv_opmode != IEEE80211_M_STA) *rssi = ieee80211_getrssi(vap); } Index: projects/release-pkg/sys/net80211/ieee80211_output.c =================================================================== --- projects/release-pkg/sys/net80211/ieee80211_output.c (revision 297604) +++ projects/release-pkg/sys/net80211/ieee80211_output.c (revision 297605) @@ -1,3507 +1,3550 @@ /*- * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting * 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 "opt_inet.h" #include "opt_inet6.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #ifdef IEEE80211_SUPPORT_TDMA #include #endif #include #include #if defined(INET) || defined(INET6) #include #endif #ifdef INET #include #include #include #endif #ifdef INET6 #include #endif #include #define ETHER_HEADER_COPY(dst, src) \ memcpy(dst, src, sizeof(struct ether_header)) /* unalligned little endian access */ #define LE_WRITE_2(p, v) do { \ ((uint8_t *)(p))[0] = (v) & 0xff; \ ((uint8_t *)(p))[1] = ((v) >> 8) & 0xff; \ } while (0) #define LE_WRITE_4(p, v) do { \ ((uint8_t *)(p))[0] = (v) & 0xff; \ ((uint8_t *)(p))[1] = ((v) >> 8) & 0xff; \ ((uint8_t *)(p))[2] = ((v) >> 16) & 0xff; \ ((uint8_t *)(p))[3] = ((v) >> 24) & 0xff; \ } while (0) static int ieee80211_fragment(struct ieee80211vap *, struct mbuf *, u_int hdrsize, u_int ciphdrsize, u_int mtu); static void ieee80211_tx_mgt_cb(struct ieee80211_node *, void *, int); #ifdef IEEE80211_DEBUG /* * Decide if an outbound management frame should be * printed when debugging is enabled. This filters some * of the less interesting frames that come frequently * (e.g. beacons). */ static __inline int doprint(struct ieee80211vap *vap, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: return (vap->iv_opmode == IEEE80211_M_IBSS); } return 1; } #endif /* * Transmit a frame to the given destination on the given VAP. * * It's up to the caller to figure out the details of who this * is going to and resolving the node. * * This routine takes care of queuing it for power save, * A-MPDU state stuff, fast-frames state stuff, encapsulation * if required, then passing it up to the driver layer. * * This routine (for now) consumes the mbuf and frees the node * reference; it ideally will return a TX status which reflects * whether the mbuf was consumed or not, so the caller can * free the mbuf (if appropriate) and the node reference (again, * if appropriate.) */ int ieee80211_vap_pkt_send_dest(struct ieee80211vap *vap, struct mbuf *m, struct ieee80211_node *ni) { struct ieee80211com *ic = vap->iv_ic; struct ifnet *ifp = vap->iv_ifp; int len, mcast; if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) && (m->m_flags & M_PWR_SAV) == 0) { /* * Station in power save mode; pass the frame * to the 802.11 layer and continue. We'll get * the frame back when the time is right. * XXX lose WDS vap linkage? */ if (ieee80211_pwrsave(ni, m) != 0) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); /* * We queued it fine, so tell the upper layer * that we consumed it. */ return (0); } /* calculate priority so drivers can find the tx queue */ if (ieee80211_classify(ni, m)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_OUTPUT, ni->ni_macaddr, NULL, "%s", "classification failure"); vap->iv_stats.is_tx_classify++; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); ieee80211_free_node(ni); /* XXX better status? */ return (0); } /* * Stash the node pointer. Note that we do this after * any call to ieee80211_dwds_mcast because that code * uses any existing value for rcvif to identify the * interface it (might have been) received on. */ m->m_pkthdr.rcvif = (void *)ni; mcast = (m->m_flags & (M_MCAST | M_BCAST)) ? 1: 0; len = m->m_pkthdr.len; BPF_MTAP(ifp, m); /* 802.3 tx */ /* * Check if A-MPDU tx aggregation is setup or if we * should try to enable it. The sta must be associated * with HT and A-MPDU enabled for use. When the policy * routine decides we should enable A-MPDU we issue an * ADDBA request and wait for a reply. The frame being * encapsulated will go out w/o using A-MPDU, or possibly * it might be collected by the driver and held/retransmit. * The default ic_ampdu_enable routine handles staggering * ADDBA requests in case the receiver NAK's us or we are * otherwise unable to establish a BA stream. */ if ((ni->ni_flags & IEEE80211_NODE_AMPDU_TX) && - (vap->iv_flags_ht & IEEE80211_FHT_AMPDU_TX) && - (m->m_flags & M_EAPOL) == 0) { - int tid = WME_AC_TO_TID(M_WME_GETAC(m)); - struct ieee80211_tx_ampdu *tap = &ni->ni_tx_ampdu[tid]; + (vap->iv_flags_ht & IEEE80211_FHT_AMPDU_TX)) { + if ((m->m_flags & M_EAPOL) == 0) { + int tid = WME_AC_TO_TID(M_WME_GETAC(m)); + struct ieee80211_tx_ampdu *tap = &ni->ni_tx_ampdu[tid]; - ieee80211_txampdu_count_packet(tap); - if (IEEE80211_AMPDU_RUNNING(tap)) { - /* - * Operational, mark frame for aggregation. - * - * XXX do tx aggregation here - */ - m->m_flags |= M_AMPDU_MPDU; - } else if (!IEEE80211_AMPDU_REQUESTED(tap) && - ic->ic_ampdu_enable(ni, tap)) { - /* - * Not negotiated yet, request service. - */ - ieee80211_ampdu_request(ni, tap); - /* XXX hold frame for reply? */ + ieee80211_txampdu_count_packet(tap); + if (IEEE80211_AMPDU_RUNNING(tap)) { + /* + * Operational, mark frame for aggregation. + * + * XXX do tx aggregation here + */ + m->m_flags |= M_AMPDU_MPDU; + } else if (!IEEE80211_AMPDU_REQUESTED(tap) && + ic->ic_ampdu_enable(ni, tap)) { + /* + * Not negotiated yet, request service. + */ + ieee80211_ampdu_request(ni, tap); + /* XXX hold frame for reply? */ + } } } +#ifdef IEEE80211_SUPPORT_SUPERG /* - * XXX If we aren't doing AMPDU TX then we /could/ do - * fast-frames encapsulation, however right now this - * output logic doesn't handle that case. + * Check for AMSDU/FF; queue for aggregation * - * So we'll be limited to "fast-frames" xmit for non-11n STA - * and "no fast frames" xmit for 11n STAs. - * It'd be nice to eventually test fast-frames out by - * gracefully falling from failing A-MPDU transmission - * (driver says no, fail to negotiate it with peer) to - * using fast-frames. + * Note: we don't bother trying to do fast frames or + * A-MSDU encapsulation for 802.3 drivers. Now, we + * likely could do it for FF (because it's a magic + * atheros tunnel LLC type) but I don't think we're going + * to really need to. For A-MSDU we'd have to set the + * A-MSDU QoS bit in the wifi header, so we just plain + * can't do it. * - * Note: we can actually put A-MSDU's inside an A-MPDU, - * so hopefully we can figure out how to make that particular - * combination work right. + * Strictly speaking, we could actually /do/ A-MSDU / FF + * with A-MPDU together which for certain circumstances + * is beneficial (eg A-MSDU of TCK ACKs.) However, + * I'll ignore that for now so existing behaviour is maintained. + * Later on it would be good to make "amsdu + ampdu" configurable. */ -#ifdef IEEE80211_SUPPORT_SUPERG - else if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF)) { - m = ieee80211_ff_check(ni, m); - if (m == NULL) { - /* NB: any ni ref held on stageq */ - return (0); + else if (__predict_true((vap->iv_caps & IEEE80211_C_8023ENCAP) == 0)) { + if ((! mcast) && ieee80211_amsdu_tx_ok(ni)) { + m = ieee80211_amsdu_check(ni, m); + if (m == NULL) { + /* NB: any ni ref held on stageq */ + IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, + "%s: amsdu_check queued frame\n", + __func__); + return (0); + } + } else if ((! mcast) && IEEE80211_ATH_CAP(vap, ni, + IEEE80211_NODE_FF)) { + m = ieee80211_ff_check(ni, m); + if (m == NULL) { + /* NB: any ni ref held on stageq */ + IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, + "%s: ff_check queued frame\n", + __func__); + return (0); + } } } #endif /* IEEE80211_SUPPORT_SUPERG */ /* * Grab the TX lock - serialise the TX process from this * point (where TX state is being checked/modified) * through to driver queue. */ IEEE80211_TX_LOCK(ic); /* * XXX make the encap and transmit code a separate function * so things like the FF (and later A-MSDU) path can just call * it for flushed frames. */ if (__predict_true((vap->iv_caps & IEEE80211_C_8023ENCAP) == 0)) { /* * Encapsulate the packet in prep for transmission. */ m = ieee80211_encap(vap, ni, m); if (m == NULL) { /* NB: stat+msg handled in ieee80211_encap */ IEEE80211_TX_UNLOCK(ic); ieee80211_free_node(ni); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return (ENOBUFS); } } (void) ieee80211_parent_xmitpkt(ic, m); /* * Unlock at this point - no need to hold it across * ieee80211_free_node() (ie, the comlock) */ IEEE80211_TX_UNLOCK(ic); ic->ic_lastdata = ticks; return (0); } /* * Send the given mbuf through the given vap. * * This consumes the mbuf regardless of whether the transmit * was successful or not. * * This does none of the initial checks that ieee80211_start() * does (eg CAC timeout, interface wakeup) - the caller must * do this first. */ static int ieee80211_start_pkt(struct ieee80211vap *vap, struct mbuf *m) { #define IS_DWDS(vap) \ (vap->iv_opmode == IEEE80211_M_WDS && \ (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY) == 0) struct ieee80211com *ic = vap->iv_ic; struct ifnet *ifp = vap->iv_ifp; struct ieee80211_node *ni; struct ether_header *eh; /* * Cancel any background scan. */ if (ic->ic_flags & IEEE80211_F_SCAN) ieee80211_cancel_anyscan(vap); /* * Find the node for the destination so we can do * things like power save and fast frames aggregation. * * NB: past this point various code assumes the first * mbuf has the 802.3 header present (and contiguous). */ ni = NULL; if (m->m_len < sizeof(struct ether_header) && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "discard frame, %s\n", "m_pullup failed"); vap->iv_stats.is_tx_nobuf++; /* XXX */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return (ENOBUFS); } eh = mtod(m, struct ether_header *); if (ETHER_IS_MULTICAST(eh->ether_dhost)) { if (IS_DWDS(vap)) { /* * Only unicast frames from the above go out * DWDS vaps; multicast frames are handled by * dispatching the frame as it comes through * the AP vap (see below). */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_WDS, eh->ether_dhost, "mcast", "%s", "on DWDS"); vap->iv_stats.is_dwds_mcast++; m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); /* XXX better status? */ return (ENOBUFS); } if (vap->iv_opmode == IEEE80211_M_HOSTAP) { /* * Spam DWDS vap's w/ multicast traffic. */ /* XXX only if dwds in use? */ ieee80211_dwds_mcast(vap, m); } } #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode != IEEE80211_M_MBSS) { #endif ni = ieee80211_find_txnode(vap, eh->ether_dhost); if (ni == NULL) { /* NB: ieee80211_find_txnode does stat+msg */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); /* XXX better status? */ return (ENOBUFS); } if (ni->ni_associd == 0 && (ni->ni_flags & IEEE80211_NODE_ASSOCID)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_OUTPUT, eh->ether_dhost, NULL, "sta not associated (type 0x%04x)", htons(eh->ether_type)); vap->iv_stats.is_tx_notassoc++; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); ieee80211_free_node(ni); /* XXX better status? */ return (ENOBUFS); } #ifdef IEEE80211_SUPPORT_MESH } else { if (!IEEE80211_ADDR_EQ(eh->ether_shost, vap->iv_myaddr)) { /* * Proxy station only if configured. */ if (!ieee80211_mesh_isproxyena(vap)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_OUTPUT | IEEE80211_MSG_MESH, eh->ether_dhost, NULL, "%s", "proxy not enabled"); vap->iv_stats.is_mesh_notproxy++; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); /* XXX better status? */ return (ENOBUFS); } IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "forward frame from DS SA(%6D), DA(%6D)\n", eh->ether_shost, ":", eh->ether_dhost, ":"); ieee80211_mesh_proxy_check(vap, eh->ether_shost); } ni = ieee80211_mesh_discover(vap, eh->ether_dhost, m); if (ni == NULL) { /* * NB: ieee80211_mesh_discover holds/disposes * frame (e.g. queueing on path discovery). */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); /* XXX better status? */ return (ENOBUFS); } } #endif /* * We've resolved the sender, so attempt to transmit it. */ if (vap->iv_state == IEEE80211_S_SLEEP) { /* * In power save; queue frame and then wakeup device * for transmit. */ ic->ic_lastdata = ticks; if (ieee80211_pwrsave(ni, m) != 0) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); ieee80211_new_state(vap, IEEE80211_S_RUN, 0); return (0); } if (ieee80211_vap_pkt_send_dest(vap, m, ni) != 0) return (ENOBUFS); return (0); #undef IS_DWDS } /* * Start method for vap's. All packets from the stack come * through here. We handle common processing of the packets * before dispatching them to the underlying device. * * if_transmit() requires that the mbuf be consumed by this call * regardless of the return condition. */ int ieee80211_vap_transmit(struct ifnet *ifp, struct mbuf *m) { struct ieee80211vap *vap = ifp->if_softc; struct ieee80211com *ic = vap->iv_ic; /* * No data frames go out unless we're running. * Note in particular this covers CAC and CSA * states (though maybe we should check muting * for CSA). */ if (vap->iv_state != IEEE80211_S_RUN && vap->iv_state != IEEE80211_S_SLEEP) { IEEE80211_LOCK(ic); /* re-check under the com lock to avoid races */ if (vap->iv_state != IEEE80211_S_RUN && vap->iv_state != IEEE80211_S_SLEEP) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "%s: ignore queue, in %s state\n", __func__, ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_tx_badstate++; IEEE80211_UNLOCK(ic); ifp->if_drv_flags |= IFF_DRV_OACTIVE; m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return (ENETDOWN); } IEEE80211_UNLOCK(ic); } /* * Sanitize mbuf flags for net80211 use. We cannot * clear M_PWR_SAV or M_MORE_DATA because these may * be set for frames that are re-submitted from the * power save queue. * * NB: This must be done before ieee80211_classify as * it marks EAPOL in frames with M_EAPOL. */ m->m_flags &= ~(M_80211_TX - M_PWR_SAV - M_MORE_DATA); /* * Bump to the packet transmission path. * The mbuf will be consumed here. */ return (ieee80211_start_pkt(vap, m)); } void ieee80211_vap_qflush(struct ifnet *ifp) { /* Empty for now */ } /* * 802.11 raw output routine. * * XXX TODO: this (and other send routines) should correctly * XXX keep the pwr mgmt bit set if it decides to call into the * XXX driver to send a frame whilst the state is SLEEP. * * Otherwise the peer may decide that we're awake and flood us * with traffic we are still too asleep to receive! */ int ieee80211_raw_output(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = vap->iv_ic; int error; /* * Set node - the caller has taken a reference, so ensure * that the mbuf has the same node value that * it would if it were going via the normal path. */ m->m_pkthdr.rcvif = (void *)ni; /* * Attempt to add bpf transmit parameters. * * For now it's ok to fail; the raw_xmit api still takes * them as an option. * * Later on when ic_raw_xmit() has params removed, * they'll have to be added - so fail the transmit if * they can't be. */ if (params) (void) ieee80211_add_xmit_params(m, params); error = ic->ic_raw_xmit(ni, m, params); if (error) { if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); } return (error); } /* * 802.11 output routine. This is (currently) used only to * connect bpf write calls to the 802.11 layer for injecting * raw 802.11 frames. */ int ieee80211_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst, struct route *ro) { #define senderr(e) do { error = (e); goto bad;} while (0) struct ieee80211_node *ni = NULL; struct ieee80211vap *vap; struct ieee80211_frame *wh; struct ieee80211com *ic = NULL; int error; int ret; if (ifp->if_drv_flags & IFF_DRV_OACTIVE) { /* * Short-circuit requests if the vap is marked OACTIVE * as this can happen because a packet came down through * ieee80211_start before the vap entered RUN state in * which case it's ok to just drop the frame. This * should not be necessary but callers of if_output don't * check OACTIVE. */ senderr(ENETDOWN); } vap = ifp->if_softc; ic = vap->iv_ic; /* * Hand to the 802.3 code if not tagged as * a raw 802.11 frame. */ if (dst->sa_family != AF_IEEE80211) return vap->iv_output(ifp, m, dst, ro); #ifdef MAC error = mac_ifnet_check_transmit(ifp, m); if (error) senderr(error); #endif if (ifp->if_flags & IFF_MONITOR) senderr(ENETDOWN); if (!IFNET_IS_UP_RUNNING(ifp)) senderr(ENETDOWN); if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT | IEEE80211_MSG_DOTH, "block %s frame in CAC state\n", "raw data"); vap->iv_stats.is_tx_badstate++; senderr(EIO); /* XXX */ } else if (vap->iv_state == IEEE80211_S_SCAN) senderr(EIO); /* XXX bypass bridge, pfil, carp, etc. */ if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_ack)) senderr(EIO); /* XXX */ wh = mtod(m, struct ieee80211_frame *); if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) != IEEE80211_FC0_VERSION_0) senderr(EIO); /* XXX */ /* locate destination node */ switch (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) { case IEEE80211_FC1_DIR_NODS: case IEEE80211_FC1_DIR_FROMDS: ni = ieee80211_find_txnode(vap, wh->i_addr1); break; case IEEE80211_FC1_DIR_TODS: case IEEE80211_FC1_DIR_DSTODS: if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) senderr(EIO); /* XXX */ ni = ieee80211_find_txnode(vap, wh->i_addr3); break; default: senderr(EIO); /* XXX */ } if (ni == NULL) { /* * Permit packets w/ bpf params through regardless * (see below about sa_len). */ if (dst->sa_len == 0) senderr(EHOSTUNREACH); ni = ieee80211_ref_node(vap->iv_bss); } /* * Sanitize mbuf for net80211 flags leaked from above. * * NB: This must be done before ieee80211_classify as * it marks EAPOL in frames with M_EAPOL. */ m->m_flags &= ~M_80211_TX; /* calculate priority so drivers can find the tx queue */ /* XXX assumes an 802.3 frame */ if (ieee80211_classify(ni, m)) senderr(EIO); /* XXX */ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); IEEE80211_NODE_STAT(ni, tx_data); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_NODE_STAT(ni, tx_mcast); m->m_flags |= M_MCAST; } else IEEE80211_NODE_STAT(ni, tx_ucast); /* NB: ieee80211_encap does not include 802.11 header */ IEEE80211_NODE_STAT_ADD(ni, tx_bytes, m->m_pkthdr.len); IEEE80211_TX_LOCK(ic); /* * NB: DLT_IEEE802_11_RADIO identifies the parameters are * present by setting the sa_len field of the sockaddr (yes, * this is a hack). * NB: we assume sa_data is suitably aligned to cast. */ ret = ieee80211_raw_output(vap, ni, m, (const struct ieee80211_bpf_params *)(dst->sa_len ? dst->sa_data : NULL)); IEEE80211_TX_UNLOCK(ic); return (ret); bad: if (m != NULL) m_freem(m); if (ni != NULL) ieee80211_free_node(ni); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return error; #undef senderr } /* * Set the direction field and address fields of an outgoing * frame. Note this should be called early on in constructing * a frame as it sets i_fc[1]; other bits can then be or'd in. */ void ieee80211_send_setup( struct ieee80211_node *ni, struct mbuf *m, int type, int tid, const uint8_t sa[IEEE80211_ADDR_LEN], const uint8_t da[IEEE80211_ADDR_LEN], const uint8_t bssid[IEEE80211_ADDR_LEN]) { #define WH4(wh) ((struct ieee80211_frame_addr4 *)wh) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_tx_ampdu *tap; struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); ieee80211_seq seqno; IEEE80211_TX_LOCK_ASSERT(ni->ni_ic); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | type; if ((type & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_DATA) { switch (vap->iv_opmode) { case IEEE80211_M_STA: wh->i_fc[1] = IEEE80211_FC1_DIR_TODS; IEEE80211_ADDR_COPY(wh->i_addr1, bssid); IEEE80211_ADDR_COPY(wh->i_addr2, sa); IEEE80211_ADDR_COPY(wh->i_addr3, da); break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, sa); IEEE80211_ADDR_COPY(wh->i_addr3, bssid); break; case IEEE80211_M_HOSTAP: wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, bssid); IEEE80211_ADDR_COPY(wh->i_addr3, sa); break; case IEEE80211_M_WDS: wh->i_fc[1] = IEEE80211_FC1_DIR_DSTODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, da); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, sa); break; case IEEE80211_M_MBSS: #ifdef IEEE80211_SUPPORT_MESH if (IEEE80211_IS_MULTICAST(da)) { wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; /* XXX next hop */ IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); } else { wh->i_fc[1] = IEEE80211_FC1_DIR_DSTODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, da); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, sa); } #endif break; case IEEE80211_M_MONITOR: /* NB: to quiet compiler */ break; } } else { wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, sa); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) IEEE80211_ADDR_COPY(wh->i_addr3, sa); else #endif IEEE80211_ADDR_COPY(wh->i_addr3, bssid); } *(uint16_t *)&wh->i_dur[0] = 0; tap = &ni->ni_tx_ampdu[tid]; if (tid != IEEE80211_NONQOS_TID && IEEE80211_AMPDU_RUNNING(tap)) m->m_flags |= M_AMPDU_MPDU; else { if (IEEE80211_HAS_SEQ(type & IEEE80211_FC0_TYPE_MASK, type & IEEE80211_FC0_SUBTYPE_MASK)) seqno = ni->ni_txseqs[tid]++; else seqno = 0; *(uint16_t *)&wh->i_seq[0] = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); M_SEQNO_SET(m, seqno); } if (IEEE80211_IS_MULTICAST(wh->i_addr1)) m->m_flags |= M_MCAST; #undef WH4 } /* * Send a management frame to the specified node. The node pointer * must have a reference as the pointer will be passed to the driver * and potentially held for a long time. If the frame is successfully * dispatched to the driver, then it is responsible for freeing the * reference (and potentially free'ing up any associated storage); * otherwise deal with reclaiming any reference (on error). */ int ieee80211_mgmt_output(struct ieee80211_node *ni, struct mbuf *m, int type, struct ieee80211_bpf_params *params) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame *wh; int ret; KASSERT(ni != NULL, ("null node")); if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT | IEEE80211_MSG_DOTH, ni, "block %s frame in CAC state", ieee80211_mgt_subtype_name[ (type & IEEE80211_FC0_SUBTYPE_MASK) >> IEEE80211_FC0_SUBTYPE_SHIFT]); vap->iv_stats.is_tx_badstate++; ieee80211_free_node(ni); m_freem(m); return EIO; /* XXX */ } M_PREPEND(m, sizeof(struct ieee80211_frame), M_NOWAIT); if (m == NULL) { ieee80211_free_node(ni); return ENOMEM; } IEEE80211_TX_LOCK(ic); wh = mtod(m, struct ieee80211_frame *); ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_MGT | type, IEEE80211_NONQOS_TID, vap->iv_myaddr, ni->ni_macaddr, ni->ni_bssid); if (params->ibp_flags & IEEE80211_BPF_CRYPTO) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr1, "encrypting frame (%s)", __func__); wh->i_fc[1] |= IEEE80211_FC1_PROTECTED; } m->m_flags |= M_ENCAP; /* mark encapsulated */ KASSERT(type != IEEE80211_FC0_SUBTYPE_PROBE_RESP, ("probe response?")); M_WME_SETAC(m, params->ibp_pri); #ifdef IEEE80211_DEBUG /* avoid printing too many frames */ if ((ieee80211_msg_debug(vap) && doprint(vap, type)) || ieee80211_msg_dumppkts(vap)) { printf("[%s] send %s on channel %u\n", ether_sprintf(wh->i_addr1), ieee80211_mgt_subtype_name[ (type & IEEE80211_FC0_SUBTYPE_MASK) >> IEEE80211_FC0_SUBTYPE_SHIFT], ieee80211_chan2ieee(ic, ic->ic_curchan)); } #endif IEEE80211_NODE_STAT(ni, tx_mgmt); ret = ieee80211_raw_output(vap, ni, m, params); IEEE80211_TX_UNLOCK(ic); return (ret); } static void ieee80211_nulldata_transmitted(struct ieee80211_node *ni, void *arg, int status) { struct ieee80211vap *vap = ni->ni_vap; wakeup(vap); } /* * Send a null data frame to the specified node. If the station * is setup for QoS then a QoS Null Data frame is constructed. * If this is a WDS station then a 4-address frame is constructed. * * NB: the caller is assumed to have setup a node reference * for use; this is necessary to deal with a race condition * when probing for inactive stations. Like ieee80211_mgmt_output * we must cleanup any node reference on error; however we * can safely just unref it as we know it will never be the * last reference to the node. */ int ieee80211_send_nulldata(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct mbuf *m; struct ieee80211_frame *wh; int hdrlen; uint8_t *frm; int ret; if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT | IEEE80211_MSG_DOTH, ni, "block %s frame in CAC state", "null data"); ieee80211_unref_node(&ni); vap->iv_stats.is_tx_badstate++; return EIO; /* XXX */ } if (ni->ni_flags & (IEEE80211_NODE_QOS|IEEE80211_NODE_HT)) hdrlen = sizeof(struct ieee80211_qosframe); else hdrlen = sizeof(struct ieee80211_frame); /* NB: only WDS vap's get 4-address frames */ if (vap->iv_opmode == IEEE80211_M_WDS) hdrlen += IEEE80211_ADDR_LEN; if (ic->ic_flags & IEEE80211_F_DATAPAD) hdrlen = roundup(hdrlen, sizeof(uint32_t)); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + hdrlen, 0); if (m == NULL) { /* XXX debug msg */ ieee80211_unref_node(&ni); vap->iv_stats.is_tx_nobuf++; return ENOMEM; } KASSERT(M_LEADINGSPACE(m) >= hdrlen, ("leading space %zd", M_LEADINGSPACE(m))); M_PREPEND(m, hdrlen, M_NOWAIT); if (m == NULL) { /* NB: cannot happen */ ieee80211_free_node(ni); return ENOMEM; } IEEE80211_TX_LOCK(ic); wh = mtod(m, struct ieee80211_frame *); /* NB: a little lie */ if (ni->ni_flags & IEEE80211_NODE_QOS) { const int tid = WME_AC_TO_TID(WME_AC_BE); uint8_t *qos; ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_DATA | IEEE80211_FC0_SUBTYPE_QOS_NULL, tid, vap->iv_myaddr, ni->ni_macaddr, ni->ni_bssid); if (vap->iv_opmode == IEEE80211_M_WDS) qos = ((struct ieee80211_qosframe_addr4 *) wh)->i_qos; else qos = ((struct ieee80211_qosframe *) wh)->i_qos; qos[0] = tid & IEEE80211_QOS_TID; if (ic->ic_wme.wme_wmeChanParams.cap_wmeParams[WME_AC_BE].wmep_noackPolicy) qos[0] |= IEEE80211_QOS_ACKPOLICY_NOACK; qos[1] = 0; } else { ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_DATA | IEEE80211_FC0_SUBTYPE_NODATA, IEEE80211_NONQOS_TID, vap->iv_myaddr, ni->ni_macaddr, ni->ni_bssid); } if (vap->iv_opmode != IEEE80211_M_WDS) { /* NB: power management bit is never sent by an AP */ if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) && vap->iv_opmode != IEEE80211_M_HOSTAP) wh->i_fc[1] |= IEEE80211_FC1_PWR_MGT; } if ((ic->ic_flags & IEEE80211_F_SCAN) && (ni->ni_flags & IEEE80211_NODE_PWR_MGT)) { ieee80211_add_callback(m, ieee80211_nulldata_transmitted, NULL); } m->m_len = m->m_pkthdr.len = hdrlen; m->m_flags |= M_ENCAP; /* mark encapsulated */ M_WME_SETAC(m, WME_AC_BE); IEEE80211_NODE_STAT(ni, tx_data); IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DUMPPKTS, ni, "send %snull data frame on channel %u, pwr mgt %s", ni->ni_flags & IEEE80211_NODE_QOS ? "QoS " : "", ieee80211_chan2ieee(ic, ic->ic_curchan), wh->i_fc[1] & IEEE80211_FC1_PWR_MGT ? "ena" : "dis"); ret = ieee80211_raw_output(vap, ni, m, NULL); IEEE80211_TX_UNLOCK(ic); return (ret); } /* * Assign priority to a frame based on any vlan tag assigned * to the station and/or any Diffserv setting in an IP header. * Finally, if an ACM policy is setup (in station mode) it's * applied. */ int ieee80211_classify(struct ieee80211_node *ni, struct mbuf *m) { const struct ether_header *eh = mtod(m, struct ether_header *); int v_wme_ac, d_wme_ac, ac; /* * Always promote PAE/EAPOL frames to high priority. */ if (eh->ether_type == htons(ETHERTYPE_PAE)) { /* NB: mark so others don't need to check header */ m->m_flags |= M_EAPOL; ac = WME_AC_VO; goto done; } /* * Non-qos traffic goes to BE. */ if ((ni->ni_flags & IEEE80211_NODE_QOS) == 0) { ac = WME_AC_BE; goto done; } /* * If node has a vlan tag then all traffic * to it must have a matching tag. */ v_wme_ac = 0; if (ni->ni_vlan != 0) { if ((m->m_flags & M_VLANTAG) == 0) { IEEE80211_NODE_STAT(ni, tx_novlantag); return 1; } if (EVL_VLANOFTAG(m->m_pkthdr.ether_vtag) != EVL_VLANOFTAG(ni->ni_vlan)) { IEEE80211_NODE_STAT(ni, tx_vlanmismatch); return 1; } /* map vlan priority to AC */ v_wme_ac = TID_TO_WME_AC(EVL_PRIOFTAG(ni->ni_vlan)); } /* XXX m_copydata may be too slow for fast path */ #ifdef INET if (eh->ether_type == htons(ETHERTYPE_IP)) { uint8_t tos; /* * IP frame, map the DSCP bits from the TOS field. */ /* NB: ip header may not be in first mbuf */ m_copydata(m, sizeof(struct ether_header) + offsetof(struct ip, ip_tos), sizeof(tos), &tos); tos >>= 5; /* NB: ECN + low 3 bits of DSCP */ d_wme_ac = TID_TO_WME_AC(tos); } else { #endif /* INET */ #ifdef INET6 if (eh->ether_type == htons(ETHERTYPE_IPV6)) { uint32_t flow; uint8_t tos; /* * IPv6 frame, map the DSCP bits from the traffic class field. */ m_copydata(m, sizeof(struct ether_header) + offsetof(struct ip6_hdr, ip6_flow), sizeof(flow), (caddr_t) &flow); tos = (uint8_t)(ntohl(flow) >> 20); tos >>= 5; /* NB: ECN + low 3 bits of DSCP */ d_wme_ac = TID_TO_WME_AC(tos); } else { #endif /* INET6 */ d_wme_ac = WME_AC_BE; #ifdef INET6 } #endif #ifdef INET } #endif /* * Use highest priority AC. */ if (v_wme_ac > d_wme_ac) ac = v_wme_ac; else ac = d_wme_ac; /* * Apply ACM policy. */ if (ni->ni_vap->iv_opmode == IEEE80211_M_STA) { static const int acmap[4] = { WME_AC_BK, /* WME_AC_BE */ WME_AC_BK, /* WME_AC_BK */ WME_AC_BE, /* WME_AC_VI */ WME_AC_VI, /* WME_AC_VO */ }; struct ieee80211com *ic = ni->ni_ic; while (ac != WME_AC_BK && ic->ic_wme.wme_wmeBssChanParams.cap_wmeParams[ac].wmep_acm) ac = acmap[ac]; } done: M_WME_SETAC(m, ac); return 0; } /* * Insure there is sufficient contiguous space to encapsulate the * 802.11 data frame. If room isn't already there, arrange for it. * Drivers and cipher modules assume we have done the necessary work * and fail rudely if they don't find the space they need. */ struct mbuf * ieee80211_mbuf_adjust(struct ieee80211vap *vap, int hdrsize, struct ieee80211_key *key, struct mbuf *m) { #define TO_BE_RECLAIMED (sizeof(struct ether_header) - sizeof(struct llc)) int needed_space = vap->iv_ic->ic_headroom + hdrsize; if (key != NULL) { /* XXX belongs in crypto code? */ needed_space += key->wk_cipher->ic_header; /* XXX frags */ /* * When crypto is being done in the host we must insure * the data are writable for the cipher routines; clone * a writable mbuf chain. * XXX handle SWMIC specially */ if (key->wk_flags & (IEEE80211_KEY_SWENCRYPT|IEEE80211_KEY_SWENMIC)) { m = m_unshare(m, M_NOWAIT); if (m == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "%s: cannot get writable mbuf\n", __func__); vap->iv_stats.is_tx_nobuf++; /* XXX new stat */ return NULL; } } } /* * We know we are called just before stripping an Ethernet * header and prepending an LLC header. This means we know * there will be * sizeof(struct ether_header) - sizeof(struct llc) * bytes recovered to which we need additional space for the * 802.11 header and any crypto header. */ /* XXX check trailing space and copy instead? */ if (M_LEADINGSPACE(m) < needed_space - TO_BE_RECLAIMED) { struct mbuf *n = m_gethdr(M_NOWAIT, m->m_type); if (n == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "%s: cannot expand storage\n", __func__); vap->iv_stats.is_tx_nobuf++; m_freem(m); return NULL; } KASSERT(needed_space <= MHLEN, ("not enough room, need %u got %d\n", needed_space, MHLEN)); /* * Setup new mbuf to have leading space to prepend the * 802.11 header and any crypto header bits that are * required (the latter are added when the driver calls * back to ieee80211_crypto_encap to do crypto encapsulation). */ /* NB: must be first 'cuz it clobbers m_data */ m_move_pkthdr(n, m); n->m_len = 0; /* NB: m_gethdr does not set */ n->m_data += needed_space; /* * Pull up Ethernet header to create the expected layout. * We could use m_pullup but that's overkill (i.e. we don't * need the actual data) and it cannot fail so do it inline * for speed. */ /* NB: struct ether_header is known to be contiguous */ n->m_len += sizeof(struct ether_header); m->m_len -= sizeof(struct ether_header); m->m_data += sizeof(struct ether_header); /* * Replace the head of the chain. */ n->m_next = m; m = n; } return m; #undef TO_BE_RECLAIMED } /* * Return the transmit key to use in sending a unicast frame. * If a unicast key is set we use that. When no unicast key is set * we fall back to the default transmit key. */ static __inline struct ieee80211_key * ieee80211_crypto_getucastkey(struct ieee80211vap *vap, struct ieee80211_node *ni) { if (IEEE80211_KEY_UNDEFINED(&ni->ni_ucastkey)) { if (vap->iv_def_txkey == IEEE80211_KEYIX_NONE || IEEE80211_KEY_UNDEFINED(&vap->iv_nw_keys[vap->iv_def_txkey])) return NULL; return &vap->iv_nw_keys[vap->iv_def_txkey]; } else { return &ni->ni_ucastkey; } } /* * Return the transmit key to use in sending a multicast frame. * Multicast traffic always uses the group key which is installed as * the default tx key. */ static __inline struct ieee80211_key * ieee80211_crypto_getmcastkey(struct ieee80211vap *vap, struct ieee80211_node *ni) { if (vap->iv_def_txkey == IEEE80211_KEYIX_NONE || IEEE80211_KEY_UNDEFINED(&vap->iv_nw_keys[vap->iv_def_txkey])) return NULL; return &vap->iv_nw_keys[vap->iv_def_txkey]; } /* * Encapsulate an outbound data frame. The mbuf chain is updated. * If an error is encountered NULL is returned. The caller is required * to provide a node reference and pullup the ethernet header in the * first mbuf. * * NB: Packet is assumed to be processed by ieee80211_classify which * marked EAPOL frames w/ M_EAPOL. */ struct mbuf * ieee80211_encap(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m) { #define WH4(wh) ((struct ieee80211_frame_addr4 *)(wh)) #define MC01(mc) ((struct ieee80211_meshcntl_ae01 *)mc) struct ieee80211com *ic = ni->ni_ic; #ifdef IEEE80211_SUPPORT_MESH struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_meshcntl_ae10 *mc; struct ieee80211_mesh_route *rt = NULL; int dir = -1; #endif struct ether_header eh; struct ieee80211_frame *wh; struct ieee80211_key *key; struct llc *llc; int hdrsize, hdrspace, datalen, addqos, txfrag, is4addr; ieee80211_seq seqno; int meshhdrsize, meshae; uint8_t *qos; + int is_amsdu = 0; IEEE80211_TX_LOCK_ASSERT(ic); /* * Copy existing Ethernet header to a safe place. The * rest of the code assumes it's ok to strip it when * reorganizing state for the final encapsulation. */ KASSERT(m->m_len >= sizeof(eh), ("no ethernet header!")); ETHER_HEADER_COPY(&eh, mtod(m, caddr_t)); /* * Insure space for additional headers. First identify * transmit key to use in calculating any buffer adjustments * required. This is also used below to do privacy * encapsulation work. Then calculate the 802.11 header * size and any padding required by the driver. * * Note key may be NULL if we fall back to the default * transmit key and that is not set. In that case the * buffer may not be expanded as needed by the cipher * routines, but they will/should discard it. */ if (vap->iv_flags & IEEE80211_F_PRIVACY) { if (vap->iv_opmode == IEEE80211_M_STA || !IEEE80211_IS_MULTICAST(eh.ether_dhost) || (vap->iv_opmode == IEEE80211_M_WDS && (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY))) key = ieee80211_crypto_getucastkey(vap, ni); else key = ieee80211_crypto_getmcastkey(vap, ni); if (key == NULL && (m->m_flags & M_EAPOL) == 0) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, eh.ether_dhost, "no default transmit key (%s) deftxkey %u", __func__, vap->iv_def_txkey); vap->iv_stats.is_tx_nodefkey++; goto bad; } } else key = NULL; /* * XXX Some ap's don't handle QoS-encapsulated EAPOL * frames so suppress use. This may be an issue if other * ap's require all data frames to be QoS-encapsulated * once negotiated in which case we'll need to make this * configurable. * NB: mesh data frames are QoS. */ addqos = ((ni->ni_flags & (IEEE80211_NODE_QOS|IEEE80211_NODE_HT)) || (vap->iv_opmode == IEEE80211_M_MBSS)) && (m->m_flags & M_EAPOL) == 0; if (addqos) hdrsize = sizeof(struct ieee80211_qosframe); else hdrsize = sizeof(struct ieee80211_frame); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) { /* * Mesh data frames are encapsulated according to the * rules of Section 11B.8.5 (p.139 of D3.0 spec). * o Group Addressed data (aka multicast) originating * at the local sta are sent w/ 3-address format and * address extension mode 00 * o Individually Addressed data (aka unicast) originating * at the local sta are sent w/ 4-address format and * address extension mode 00 * o Group Addressed data forwarded from a non-mesh sta are * sent w/ 3-address format and address extension mode 01 * o Individually Address data from another sta are sent * w/ 4-address format and address extension mode 10 */ is4addr = 0; /* NB: don't use, disable */ if (!IEEE80211_IS_MULTICAST(eh.ether_dhost)) { rt = ieee80211_mesh_rt_find(vap, eh.ether_dhost); KASSERT(rt != NULL, ("route is NULL")); dir = IEEE80211_FC1_DIR_DSTODS; hdrsize += IEEE80211_ADDR_LEN; if (rt->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY) { if (IEEE80211_ADDR_EQ(rt->rt_mesh_gate, vap->iv_myaddr)) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, eh.ether_dhost, "%s", "trying to send to ourself"); goto bad; } meshae = IEEE80211_MESH_AE_10; meshhdrsize = sizeof(struct ieee80211_meshcntl_ae10); } else { meshae = IEEE80211_MESH_AE_00; meshhdrsize = sizeof(struct ieee80211_meshcntl); } } else { dir = IEEE80211_FC1_DIR_FROMDS; if (!IEEE80211_ADDR_EQ(eh.ether_shost, vap->iv_myaddr)) { /* proxy group */ meshae = IEEE80211_MESH_AE_01; meshhdrsize = sizeof(struct ieee80211_meshcntl_ae01); } else { /* group */ meshae = IEEE80211_MESH_AE_00; meshhdrsize = sizeof(struct ieee80211_meshcntl); } } } else { #endif /* * 4-address frames need to be generated for: * o packets sent through a WDS vap (IEEE80211_M_WDS) * o packets sent through a vap marked for relaying * (e.g. a station operating with dynamic WDS) */ is4addr = vap->iv_opmode == IEEE80211_M_WDS || ((vap->iv_flags_ext & IEEE80211_FEXT_4ADDR) && !IEEE80211_ADDR_EQ(eh.ether_shost, vap->iv_myaddr)); if (is4addr) hdrsize += IEEE80211_ADDR_LEN; meshhdrsize = meshae = 0; #ifdef IEEE80211_SUPPORT_MESH } #endif /* * Honor driver DATAPAD requirement. */ if (ic->ic_flags & IEEE80211_F_DATAPAD) hdrspace = roundup(hdrsize, sizeof(uint32_t)); else hdrspace = hdrsize; if (__predict_true((m->m_flags & M_FF) == 0)) { /* * Normal frame. */ m = ieee80211_mbuf_adjust(vap, hdrspace + meshhdrsize, key, m); if (m == NULL) { /* NB: ieee80211_mbuf_adjust handles msgs+statistics */ goto bad; } /* NB: this could be optimized 'cuz of ieee80211_mbuf_adjust */ m_adj(m, sizeof(struct ether_header) - sizeof(struct llc)); llc = mtod(m, struct llc *); llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP; llc->llc_control = LLC_UI; llc->llc_snap.org_code[0] = 0; llc->llc_snap.org_code[1] = 0; llc->llc_snap.org_code[2] = 0; llc->llc_snap.ether_type = eh.ether_type; } else { #ifdef IEEE80211_SUPPORT_SUPERG /* - * Aggregated frame. + * Aggregated frame. Check if it's for AMSDU or FF. + * + * XXX TODO: IEEE80211_NODE_AMSDU* isn't implemented + * anywhere for some reason. But, since 11n requires + * AMSDU RX, we can just assume "11n" == "AMSDU". */ - m = ieee80211_ff_encap(vap, m, hdrspace + meshhdrsize, key); + IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: called; M_FF\n", __func__); + if (ieee80211_amsdu_tx_ok(ni)) { + m = ieee80211_amsdu_encap(vap, m, hdrspace + meshhdrsize, key); + is_amsdu = 1; + } else { + m = ieee80211_ff_encap(vap, m, hdrspace + meshhdrsize, key); + } if (m == NULL) #endif goto bad; } datalen = m->m_pkthdr.len; /* NB: w/o 802.11 header */ M_PREPEND(m, hdrspace + meshhdrsize, M_NOWAIT); if (m == NULL) { vap->iv_stats.is_tx_nobuf++; goto bad; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_DATA; *(uint16_t *)wh->i_dur = 0; qos = NULL; /* NB: quiet compiler */ if (is4addr) { wh->i_fc[1] = IEEE80211_FC1_DIR_DSTODS; IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_macaddr); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_dhost); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, eh.ether_shost); } else switch (vap->iv_opmode) { case IEEE80211_M_STA: wh->i_fc[1] = IEEE80211_FC1_DIR_TODS; IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_bssid); IEEE80211_ADDR_COPY(wh->i_addr2, eh.ether_shost); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_dhost); break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, eh.ether_shost); /* * NB: always use the bssid from iv_bss as the * neighbor's may be stale after an ibss merge */ IEEE80211_ADDR_COPY(wh->i_addr3, vap->iv_bss->ni_bssid); break; case IEEE80211_M_HOSTAP: wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, ni->ni_bssid); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_shost); break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_M_MBSS: /* NB: offset by hdrspace to deal with DATAPAD */ mc = (struct ieee80211_meshcntl_ae10 *) (mtod(m, uint8_t *) + hdrspace); wh->i_fc[1] = dir; switch (meshae) { case IEEE80211_MESH_AE_00: /* no proxy */ mc->mc_flags = 0; if (dir == IEEE80211_FC1_DIR_DSTODS) { /* ucast */ IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_macaddr); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_dhost); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, eh.ether_shost); qos =((struct ieee80211_qosframe_addr4 *) wh)->i_qos; } else if (dir == IEEE80211_FC1_DIR_FROMDS) { /* mcast */ IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_shost); qos = ((struct ieee80211_qosframe *) wh)->i_qos; } break; case IEEE80211_MESH_AE_01: /* mcast, proxy */ wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, vap->iv_myaddr); mc->mc_flags = 1; IEEE80211_ADDR_COPY(MC01(mc)->mc_addr4, eh.ether_shost); qos = ((struct ieee80211_qosframe *) wh)->i_qos; break; case IEEE80211_MESH_AE_10: /* ucast, proxy */ KASSERT(rt != NULL, ("route is NULL")); IEEE80211_ADDR_COPY(wh->i_addr1, rt->rt_nexthop); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, rt->rt_mesh_gate); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, vap->iv_myaddr); mc->mc_flags = IEEE80211_MESH_AE_10; IEEE80211_ADDR_COPY(mc->mc_addr5, eh.ether_dhost); IEEE80211_ADDR_COPY(mc->mc_addr6, eh.ether_shost); qos = ((struct ieee80211_qosframe_addr4 *) wh)->i_qos; break; default: KASSERT(0, ("meshae %d", meshae)); break; } mc->mc_ttl = ms->ms_ttl; ms->ms_seq++; LE_WRITE_4(mc->mc_seq, ms->ms_seq); break; #endif case IEEE80211_M_WDS: /* NB: is4addr should always be true */ default: goto bad; } if (m->m_flags & M_MORE_DATA) wh->i_fc[1] |= IEEE80211_FC1_MORE_DATA; if (addqos) { int ac, tid; if (is4addr) { qos = ((struct ieee80211_qosframe_addr4 *) wh)->i_qos; /* NB: mesh case handled earlier */ } else if (vap->iv_opmode != IEEE80211_M_MBSS) qos = ((struct ieee80211_qosframe *) wh)->i_qos; ac = M_WME_GETAC(m); /* map from access class/queue to 11e header priorty value */ tid = WME_AC_TO_TID(ac); qos[0] = tid & IEEE80211_QOS_TID; if (ic->ic_wme.wme_wmeChanParams.cap_wmeParams[ac].wmep_noackPolicy) qos[0] |= IEEE80211_QOS_ACKPOLICY_NOACK; #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) qos[1] = IEEE80211_QOS_MC; else #endif qos[1] = 0; wh->i_fc[0] |= IEEE80211_FC0_SUBTYPE_QOS; + /* + * If this is an A-MSDU then ensure we set the + * relevant field. + */ + if (is_amsdu) + qos[0] |= IEEE80211_QOS_AMSDU; + if ((m->m_flags & M_AMPDU_MPDU) == 0) { /* * NB: don't assign a sequence # to potential * aggregates; we expect this happens at the * point the frame comes off any aggregation q * as otherwise we may introduce holes in the * BA sequence space and/or make window accouting * more difficult. * * XXX may want to control this with a driver * capability; this may also change when we pull * aggregation up into net80211 */ seqno = ni->ni_txseqs[tid]++; *(uint16_t *)wh->i_seq = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); M_SEQNO_SET(m, seqno); } } else { seqno = ni->ni_txseqs[IEEE80211_NONQOS_TID]++; *(uint16_t *)wh->i_seq = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); M_SEQNO_SET(m, seqno); + + /* + * XXX TODO: we shouldn't allow EAPOL, etc that would + * be forced to be non-QoS traffic to be A-MSDU encapsulated. + */ + if (is_amsdu) + printf("%s: XXX ERROR: is_amsdu set; not QoS!\n", + __func__); } /* check if xmit fragmentation is required */ txfrag = (m->m_pkthdr.len > vap->iv_fragthreshold && !IEEE80211_IS_MULTICAST(wh->i_addr1) && (vap->iv_caps & IEEE80211_C_TXFRAG) && (m->m_flags & (M_FF | M_AMPDU_MPDU)) == 0); if (key != NULL) { /* * IEEE 802.1X: send EAPOL frames always in the clear. * WPA/WPA2: encrypt EAPOL keys when pairwise keys are set. */ if ((m->m_flags & M_EAPOL) == 0 || ((vap->iv_flags & IEEE80211_F_WPA) && (vap->iv_opmode == IEEE80211_M_STA ? !IEEE80211_KEY_UNDEFINED(key) : !IEEE80211_KEY_UNDEFINED(&ni->ni_ucastkey)))) { wh->i_fc[1] |= IEEE80211_FC1_PROTECTED; if (!ieee80211_crypto_enmic(vap, key, m, txfrag)) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_OUTPUT, eh.ether_dhost, "%s", "enmic failed, discard frame"); vap->iv_stats.is_crypto_enmicfail++; goto bad; } } } if (txfrag && !ieee80211_fragment(vap, m, hdrsize, key != NULL ? key->wk_cipher->ic_header : 0, vap->iv_fragthreshold)) goto bad; m->m_flags |= M_ENCAP; /* mark encapsulated */ IEEE80211_NODE_STAT(ni, tx_data); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_NODE_STAT(ni, tx_mcast); m->m_flags |= M_MCAST; } else IEEE80211_NODE_STAT(ni, tx_ucast); IEEE80211_NODE_STAT_ADD(ni, tx_bytes, datalen); return m; bad: if (m != NULL) m_freem(m); return NULL; #undef WH4 #undef MC01 } void ieee80211_free_mbuf(struct mbuf *m) { struct mbuf *next; if (m == NULL) return; do { next = m->m_nextpkt; m->m_nextpkt = NULL; m_freem(m); } while ((m = next) != NULL); } /* * Fragment the frame according to the specified mtu. * The size of the 802.11 header (w/o padding) is provided * so we don't need to recalculate it. We create a new * mbuf for each fragment and chain it through m_nextpkt; * we might be able to optimize this by reusing the original * packet's mbufs but that is significantly more complicated. */ static int ieee80211_fragment(struct ieee80211vap *vap, struct mbuf *m0, u_int hdrsize, u_int ciphdrsize, u_int mtu) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_frame *wh, *whf; struct mbuf *m, *prev; u_int totalhdrsize, fragno, fragsize, off, remainder, payload; u_int hdrspace; KASSERT(m0->m_nextpkt == NULL, ("mbuf already chained?")); KASSERT(m0->m_pkthdr.len > mtu, ("pktlen %u mtu %u", m0->m_pkthdr.len, mtu)); /* * Honor driver DATAPAD requirement. */ if (ic->ic_flags & IEEE80211_F_DATAPAD) hdrspace = roundup(hdrsize, sizeof(uint32_t)); else hdrspace = hdrsize; wh = mtod(m0, struct ieee80211_frame *); /* NB: mark the first frag; it will be propagated below */ wh->i_fc[1] |= IEEE80211_FC1_MORE_FRAG; totalhdrsize = hdrspace + ciphdrsize; fragno = 1; off = mtu - ciphdrsize; remainder = m0->m_pkthdr.len - off; prev = m0; do { fragsize = totalhdrsize + remainder; if (fragsize > mtu) fragsize = mtu; /* XXX fragsize can be >2048! */ KASSERT(fragsize < MCLBYTES, ("fragment size %u too big!", fragsize)); if (fragsize > MHLEN) m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); else m = m_gethdr(M_NOWAIT, MT_DATA); if (m == NULL) goto bad; /* leave room to prepend any cipher header */ m_align(m, fragsize - ciphdrsize); /* * Form the header in the fragment. Note that since * we mark the first fragment with the MORE_FRAG bit * it automatically is propagated to each fragment; we * need only clear it on the last fragment (done below). * NB: frag 1+ dont have Mesh Control field present. */ whf = mtod(m, struct ieee80211_frame *); memcpy(whf, wh, hdrsize); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) { if (IEEE80211_IS_DSTODS(wh)) ((struct ieee80211_qosframe_addr4 *) whf)->i_qos[1] &= ~IEEE80211_QOS_MC; else ((struct ieee80211_qosframe *) whf)->i_qos[1] &= ~IEEE80211_QOS_MC; } #endif *(uint16_t *)&whf->i_seq[0] |= htole16( (fragno & IEEE80211_SEQ_FRAG_MASK) << IEEE80211_SEQ_FRAG_SHIFT); fragno++; payload = fragsize - totalhdrsize; /* NB: destination is known to be contiguous */ m_copydata(m0, off, payload, mtod(m, uint8_t *) + hdrspace); m->m_len = hdrspace + payload; m->m_pkthdr.len = hdrspace + payload; m->m_flags |= M_FRAG; /* chain up the fragment */ prev->m_nextpkt = m; prev = m; /* deduct fragment just formed */ remainder -= payload; off += payload; } while (remainder != 0); /* set the last fragment */ m->m_flags |= M_LASTFRAG; whf->i_fc[1] &= ~IEEE80211_FC1_MORE_FRAG; /* strip first mbuf now that everything has been copied */ m_adj(m0, -(m0->m_pkthdr.len - (mtu - ciphdrsize))); m0->m_flags |= M_FIRSTFRAG | M_FRAG; vap->iv_stats.is_tx_fragframes++; vap->iv_stats.is_tx_frags += fragno-1; return 1; bad: /* reclaim fragments but leave original frame for caller to free */ ieee80211_free_mbuf(m0->m_nextpkt); m0->m_nextpkt = NULL; return 0; } /* * Add a supported rates element id to a frame. */ uint8_t * ieee80211_add_rates(uint8_t *frm, const struct ieee80211_rateset *rs) { int nrates; *frm++ = IEEE80211_ELEMID_RATES; nrates = rs->rs_nrates; if (nrates > IEEE80211_RATE_SIZE) nrates = IEEE80211_RATE_SIZE; *frm++ = nrates; memcpy(frm, rs->rs_rates, nrates); return frm + nrates; } /* * Add an extended supported rates element id to a frame. */ uint8_t * ieee80211_add_xrates(uint8_t *frm, const struct ieee80211_rateset *rs) { /* * Add an extended supported rates element if operating in 11g mode. */ if (rs->rs_nrates > IEEE80211_RATE_SIZE) { int nrates = rs->rs_nrates - IEEE80211_RATE_SIZE; *frm++ = IEEE80211_ELEMID_XRATES; *frm++ = nrates; memcpy(frm, rs->rs_rates + IEEE80211_RATE_SIZE, nrates); frm += nrates; } return frm; } /* * Add an ssid element to a frame. */ uint8_t * ieee80211_add_ssid(uint8_t *frm, const uint8_t *ssid, u_int len) { *frm++ = IEEE80211_ELEMID_SSID; *frm++ = len; memcpy(frm, ssid, len); return frm + len; } /* * Add an erp element to a frame. */ static uint8_t * ieee80211_add_erp(uint8_t *frm, struct ieee80211com *ic) { uint8_t erp; *frm++ = IEEE80211_ELEMID_ERP; *frm++ = 1; erp = 0; if (ic->ic_nonerpsta != 0) erp |= IEEE80211_ERP_NON_ERP_PRESENT; if (ic->ic_flags & IEEE80211_F_USEPROT) erp |= IEEE80211_ERP_USE_PROTECTION; if (ic->ic_flags & IEEE80211_F_USEBARKER) erp |= IEEE80211_ERP_LONG_PREAMBLE; *frm++ = erp; return frm; } /* * Add a CFParams element to a frame. */ static uint8_t * ieee80211_add_cfparms(uint8_t *frm, struct ieee80211com *ic) { #define ADDSHORT(frm, v) do { \ LE_WRITE_2(frm, v); \ frm += 2; \ } while (0) *frm++ = IEEE80211_ELEMID_CFPARMS; *frm++ = 6; *frm++ = 0; /* CFP count */ *frm++ = 2; /* CFP period */ ADDSHORT(frm, 0); /* CFP MaxDuration (TU) */ ADDSHORT(frm, 0); /* CFP CurRemaining (TU) */ return frm; #undef ADDSHORT } static __inline uint8_t * add_appie(uint8_t *frm, const struct ieee80211_appie *ie) { memcpy(frm, ie->ie_data, ie->ie_len); return frm + ie->ie_len; } static __inline uint8_t * add_ie(uint8_t *frm, const uint8_t *ie) { memcpy(frm, ie, 2 + ie[1]); return frm + 2 + ie[1]; } #define WME_OUI_BYTES 0x00, 0x50, 0xf2 /* * Add a WME information element to a frame. */ uint8_t * ieee80211_add_wme_info(uint8_t *frm, struct ieee80211_wme_state *wme) { static const struct ieee80211_wme_info info = { .wme_id = IEEE80211_ELEMID_VENDOR, .wme_len = sizeof(struct ieee80211_wme_info) - 2, .wme_oui = { WME_OUI_BYTES }, .wme_type = WME_OUI_TYPE, .wme_subtype = WME_INFO_OUI_SUBTYPE, .wme_version = WME_VERSION, .wme_info = 0, }; memcpy(frm, &info, sizeof(info)); return frm + sizeof(info); } /* * Add a WME parameters element to a frame. */ static uint8_t * ieee80211_add_wme_param(uint8_t *frm, struct ieee80211_wme_state *wme) { #define SM(_v, _f) (((_v) << _f##_S) & _f) #define ADDSHORT(frm, v) do { \ LE_WRITE_2(frm, v); \ frm += 2; \ } while (0) /* NB: this works 'cuz a param has an info at the front */ static const struct ieee80211_wme_info param = { .wme_id = IEEE80211_ELEMID_VENDOR, .wme_len = sizeof(struct ieee80211_wme_param) - 2, .wme_oui = { WME_OUI_BYTES }, .wme_type = WME_OUI_TYPE, .wme_subtype = WME_PARAM_OUI_SUBTYPE, .wme_version = WME_VERSION, }; int i; memcpy(frm, ¶m, sizeof(param)); frm += __offsetof(struct ieee80211_wme_info, wme_info); *frm++ = wme->wme_bssChanParams.cap_info; /* AC info */ *frm++ = 0; /* reserved field */ for (i = 0; i < WME_NUM_AC; i++) { const struct wmeParams *ac = &wme->wme_bssChanParams.cap_wmeParams[i]; *frm++ = SM(i, WME_PARAM_ACI) | SM(ac->wmep_acm, WME_PARAM_ACM) | SM(ac->wmep_aifsn, WME_PARAM_AIFSN) ; *frm++ = SM(ac->wmep_logcwmax, WME_PARAM_LOGCWMAX) | SM(ac->wmep_logcwmin, WME_PARAM_LOGCWMIN) ; ADDSHORT(frm, ac->wmep_txopLimit); } return frm; #undef SM #undef ADDSHORT } #undef WME_OUI_BYTES /* * Add an 11h Power Constraint element to a frame. */ static uint8_t * ieee80211_add_powerconstraint(uint8_t *frm, struct ieee80211vap *vap) { const struct ieee80211_channel *c = vap->iv_bss->ni_chan; /* XXX per-vap tx power limit? */ int8_t limit = vap->iv_ic->ic_txpowlimit / 2; frm[0] = IEEE80211_ELEMID_PWRCNSTR; frm[1] = 1; frm[2] = c->ic_maxregpower > limit ? c->ic_maxregpower - limit : 0; return frm + 3; } /* * Add an 11h Power Capability element to a frame. */ static uint8_t * ieee80211_add_powercapability(uint8_t *frm, const struct ieee80211_channel *c) { frm[0] = IEEE80211_ELEMID_PWRCAP; frm[1] = 2; frm[2] = c->ic_minpower; frm[3] = c->ic_maxpower; return frm + 4; } /* * Add an 11h Supported Channels element to a frame. */ static uint8_t * ieee80211_add_supportedchannels(uint8_t *frm, struct ieee80211com *ic) { static const int ielen = 26; frm[0] = IEEE80211_ELEMID_SUPPCHAN; frm[1] = ielen; /* XXX not correct */ memcpy(frm+2, ic->ic_chan_avail, ielen); return frm + 2 + ielen; } /* * Add an 11h Quiet time element to a frame. */ static uint8_t * ieee80211_add_quiet(uint8_t *frm, struct ieee80211vap *vap) { struct ieee80211_quiet_ie *quiet = (struct ieee80211_quiet_ie *) frm; quiet->quiet_ie = IEEE80211_ELEMID_QUIET; quiet->len = 6; if (vap->iv_quiet_count_value == 1) vap->iv_quiet_count_value = vap->iv_quiet_count; else if (vap->iv_quiet_count_value > 1) vap->iv_quiet_count_value--; if (vap->iv_quiet_count_value == 0) { /* value 0 is reserved as per 802.11h standerd */ vap->iv_quiet_count_value = 1; } quiet->tbttcount = vap->iv_quiet_count_value; quiet->period = vap->iv_quiet_period; quiet->duration = htole16(vap->iv_quiet_duration); quiet->offset = htole16(vap->iv_quiet_offset); return frm + sizeof(*quiet); } /* * Add an 11h Channel Switch Announcement element to a frame. * Note that we use the per-vap CSA count to adjust the global * counter so we can use this routine to form probe response * frames and get the current count. */ static uint8_t * ieee80211_add_csa(uint8_t *frm, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_csa_ie *csa = (struct ieee80211_csa_ie *) frm; csa->csa_ie = IEEE80211_ELEMID_CSA; csa->csa_len = 3; csa->csa_mode = 1; /* XXX force quiet on channel */ csa->csa_newchan = ieee80211_chan2ieee(ic, ic->ic_csa_newchan); csa->csa_count = ic->ic_csa_count - vap->iv_csa_count; return frm + sizeof(*csa); } /* * Add an 11h country information element to a frame. */ static uint8_t * ieee80211_add_countryie(uint8_t *frm, struct ieee80211com *ic) { if (ic->ic_countryie == NULL || ic->ic_countryie_chan != ic->ic_bsschan) { /* * Handle lazy construction of ie. This is done on * first use and after a channel change that requires * re-calculation. */ if (ic->ic_countryie != NULL) IEEE80211_FREE(ic->ic_countryie, M_80211_NODE_IE); ic->ic_countryie = ieee80211_alloc_countryie(ic); if (ic->ic_countryie == NULL) return frm; ic->ic_countryie_chan = ic->ic_bsschan; } return add_appie(frm, ic->ic_countryie); } uint8_t * ieee80211_add_wpa(uint8_t *frm, const struct ieee80211vap *vap) { if (vap->iv_flags & IEEE80211_F_WPA1 && vap->iv_wpa_ie != NULL) return (add_ie(frm, vap->iv_wpa_ie)); else { /* XXX else complain? */ return (frm); } } uint8_t * ieee80211_add_rsn(uint8_t *frm, const struct ieee80211vap *vap) { if (vap->iv_flags & IEEE80211_F_WPA2 && vap->iv_rsn_ie != NULL) return (add_ie(frm, vap->iv_rsn_ie)); else { /* XXX else complain? */ return (frm); } } uint8_t * ieee80211_add_qos(uint8_t *frm, const struct ieee80211_node *ni) { if (ni->ni_flags & IEEE80211_NODE_QOS) { *frm++ = IEEE80211_ELEMID_QOS; *frm++ = 1; *frm++ = 0; } return (frm); } /* * Send a probe request frame with the specified ssid * and any optional information element data. */ int ieee80211_send_probereq(struct ieee80211_node *ni, const uint8_t sa[IEEE80211_ADDR_LEN], const uint8_t da[IEEE80211_ADDR_LEN], const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t *ssid, size_t ssidlen) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; const struct ieee80211_txparam *tp; struct ieee80211_bpf_params params; struct ieee80211_frame *wh; const struct ieee80211_rateset *rs; struct mbuf *m; uint8_t *frm; int ret; if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT, ni, "block %s frame in CAC state", "probe request"); vap->iv_stats.is_tx_badstate++; return EIO; /* XXX */ } /* * Hold a reference on the node so it doesn't go away until after * the xmit is complete all the way in the driver. On error we * will remove our reference. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); /* * prreq frame format * [tlv] ssid * [tlv] supported rates * [tlv] RSN (optional) * [tlv] extended supported rates * [tlv] WPA (optional) * [tlv] user-specified ie's */ m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + sizeof(struct ieee80211_ie_wpa) + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + sizeof(struct ieee80211_ie_wpa) + (vap->iv_appie_probereq != NULL ? vap->iv_appie_probereq->ie_len : 0) ); if (m == NULL) { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } frm = ieee80211_add_ssid(frm, ssid, ssidlen); rs = ieee80211_get_suprates(ic, ic->ic_curchan); frm = ieee80211_add_rates(frm, rs); frm = ieee80211_add_rsn(frm, vap); frm = ieee80211_add_xrates(frm, rs); frm = ieee80211_add_wpa(frm, vap); if (vap->iv_appie_probereq != NULL) frm = add_appie(frm, vap->iv_appie_probereq); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); KASSERT(M_LEADINGSPACE(m) >= sizeof(struct ieee80211_frame), ("leading space %zd", M_LEADINGSPACE(m))); M_PREPEND(m, sizeof(struct ieee80211_frame), M_NOWAIT); if (m == NULL) { /* NB: cannot happen */ ieee80211_free_node(ni); return ENOMEM; } IEEE80211_TX_LOCK(ic); wh = mtod(m, struct ieee80211_frame *); ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_REQ, IEEE80211_NONQOS_TID, sa, da, bssid); /* XXX power management? */ m->m_flags |= M_ENCAP; /* mark encapsulated */ M_WME_SETAC(m, WME_AC_BE); IEEE80211_NODE_STAT(ni, tx_probereq); IEEE80211_NODE_STAT(ni, tx_mgmt); IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DUMPPKTS, "send probe req on channel %u bssid %s ssid \"%.*s\"\n", ieee80211_chan2ieee(ic, ic->ic_curchan), ether_sprintf(bssid), ssidlen, ssid); memset(¶ms, 0, sizeof(params)); params.ibp_pri = M_WME_GETAC(m); tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; params.ibp_rate0 = tp->mgmtrate; if (IEEE80211_IS_MULTICAST(da)) { params.ibp_flags |= IEEE80211_BPF_NOACK; params.ibp_try0 = 1; } else params.ibp_try0 = tp->maxretry; params.ibp_power = ni->ni_txpower; ret = ieee80211_raw_output(vap, ni, m, ¶ms); IEEE80211_TX_UNLOCK(ic); return (ret); } /* * Calculate capability information for mgt frames. */ uint16_t ieee80211_getcapinfo(struct ieee80211vap *vap, struct ieee80211_channel *chan) { struct ieee80211com *ic = vap->iv_ic; uint16_t capinfo; KASSERT(vap->iv_opmode != IEEE80211_M_STA, ("station mode")); if (vap->iv_opmode == IEEE80211_M_HOSTAP) capinfo = IEEE80211_CAPINFO_ESS; else if (vap->iv_opmode == IEEE80211_M_IBSS) capinfo = IEEE80211_CAPINFO_IBSS; else capinfo = 0; if (vap->iv_flags & IEEE80211_F_PRIVACY) capinfo |= IEEE80211_CAPINFO_PRIVACY; if ((ic->ic_flags & IEEE80211_F_SHPREAMBLE) && IEEE80211_IS_CHAN_2GHZ(chan)) capinfo |= IEEE80211_CAPINFO_SHORT_PREAMBLE; if (ic->ic_flags & IEEE80211_F_SHSLOT) capinfo |= IEEE80211_CAPINFO_SHORT_SLOTTIME; if (IEEE80211_IS_CHAN_5GHZ(chan) && (vap->iv_flags & IEEE80211_F_DOTH)) capinfo |= IEEE80211_CAPINFO_SPECTRUM_MGMT; return capinfo; } /* * Send a management frame. The node is for the destination (or ic_bss * when in station mode). Nodes other than ic_bss have their reference * count bumped to reflect our use for an indeterminant time. */ int ieee80211_send_mgmt(struct ieee80211_node *ni, int type, int arg) { #define HTFLAGS (IEEE80211_NODE_HT | IEEE80211_NODE_HTCOMPAT) #define senderr(_x, _v) do { vap->iv_stats._v++; ret = _x; goto bad; } while (0) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_node *bss = vap->iv_bss; struct ieee80211_bpf_params params; struct mbuf *m; uint8_t *frm; uint16_t capinfo; int has_challenge, is_shared_key, ret, status; KASSERT(ni != NULL, ("null node")); /* * Hold a reference on the node so it doesn't go away until after * the xmit is complete all the way in the driver. On error we * will remove our reference. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); memset(¶ms, 0, sizeof(params)); switch (type) { case IEEE80211_FC0_SUBTYPE_AUTH: status = arg >> 16; arg &= 0xffff; has_challenge = ((arg == IEEE80211_AUTH_SHARED_CHALLENGE || arg == IEEE80211_AUTH_SHARED_RESPONSE) && ni->ni_challenge != NULL); /* * Deduce whether we're doing open authentication or * shared key authentication. We do the latter if * we're in the middle of a shared key authentication * handshake or if we're initiating an authentication * request and configured to use shared key. */ is_shared_key = has_challenge || arg >= IEEE80211_AUTH_SHARED_RESPONSE || (arg == IEEE80211_AUTH_SHARED_REQUEST && bss->ni_authmode == IEEE80211_AUTH_SHARED); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), 3 * sizeof(uint16_t) + (has_challenge && status == IEEE80211_STATUS_SUCCESS ? sizeof(uint16_t)+IEEE80211_CHALLENGE_LEN : 0) ); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); ((uint16_t *)frm)[0] = (is_shared_key) ? htole16(IEEE80211_AUTH_ALG_SHARED) : htole16(IEEE80211_AUTH_ALG_OPEN); ((uint16_t *)frm)[1] = htole16(arg); /* sequence number */ ((uint16_t *)frm)[2] = htole16(status);/* status */ if (has_challenge && status == IEEE80211_STATUS_SUCCESS) { ((uint16_t *)frm)[3] = htole16((IEEE80211_CHALLENGE_LEN << 8) | IEEE80211_ELEMID_CHALLENGE); memcpy(&((uint16_t *)frm)[4], ni->ni_challenge, IEEE80211_CHALLENGE_LEN); m->m_pkthdr.len = m->m_len = 4 * sizeof(uint16_t) + IEEE80211_CHALLENGE_LEN; if (arg == IEEE80211_AUTH_SHARED_RESPONSE) { IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "request encrypt frame (%s)", __func__); /* mark frame for encryption */ params.ibp_flags |= IEEE80211_BPF_CRYPTO; } } else m->m_pkthdr.len = m->m_len = 3 * sizeof(uint16_t); /* XXX not right for shared key */ if (status == IEEE80211_STATUS_SUCCESS) IEEE80211_NODE_STAT(ni, tx_auth); else IEEE80211_NODE_STAT(ni, tx_auth_fail); if (vap->iv_opmode == IEEE80211_M_STA) ieee80211_add_callback(m, ieee80211_tx_mgt_cb, (void *) vap->iv_state); break; case IEEE80211_FC0_SUBTYPE_DEAUTH: IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "send station deauthenticate (reason %d)", arg); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t)); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); *(uint16_t *)frm = htole16(arg); /* reason */ m->m_pkthdr.len = m->m_len = sizeof(uint16_t); IEEE80211_NODE_STAT(ni, tx_deauth); IEEE80211_NODE_STAT_SET(ni, tx_deauth_code, arg); ieee80211_node_unauthorize(ni); /* port closed */ break; case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: /* * asreq frame format * [2] capability information * [2] listen interval * [6*] current AP address (reassoc only) * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates * [4] power capability (optional) * [28] supported channels (optional) * [tlv] HT capabilities * [tlv] WME (optional) * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Atheros capabilities (if negotiated) * [tlv] AppIE's (optional) */ m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) + sizeof(uint16_t) + IEEE80211_ADDR_LEN + 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + 4 + 2 + 26 + sizeof(struct ieee80211_wme_info) + sizeof(struct ieee80211_ie_htcap) + 4 + sizeof(struct ieee80211_ie_htcap) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) #endif + (vap->iv_appie_wpa != NULL ? vap->iv_appie_wpa->ie_len : 0) + (vap->iv_appie_assocreq != NULL ? vap->iv_appie_assocreq->ie_len : 0) ); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode %u", vap->iv_opmode)); capinfo = IEEE80211_CAPINFO_ESS; if (vap->iv_flags & IEEE80211_F_PRIVACY) capinfo |= IEEE80211_CAPINFO_PRIVACY; /* * NB: Some 11a AP's reject the request when * short premable is set. */ if ((ic->ic_flags & IEEE80211_F_SHPREAMBLE) && IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) capinfo |= IEEE80211_CAPINFO_SHORT_PREAMBLE; if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && (ic->ic_caps & IEEE80211_C_SHSLOT)) capinfo |= IEEE80211_CAPINFO_SHORT_SLOTTIME; if ((ni->ni_capinfo & IEEE80211_CAPINFO_SPECTRUM_MGMT) && (vap->iv_flags & IEEE80211_F_DOTH)) capinfo |= IEEE80211_CAPINFO_SPECTRUM_MGMT; *(uint16_t *)frm = htole16(capinfo); frm += 2; KASSERT(bss->ni_intval != 0, ("beacon interval is zero!")); *(uint16_t *)frm = htole16(howmany(ic->ic_lintval, bss->ni_intval)); frm += 2; if (type == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) { IEEE80211_ADDR_COPY(frm, bss->ni_bssid); frm += IEEE80211_ADDR_LEN; } frm = ieee80211_add_ssid(frm, ni->ni_essid, ni->ni_esslen); frm = ieee80211_add_rates(frm, &ni->ni_rates); frm = ieee80211_add_rsn(frm, vap); frm = ieee80211_add_xrates(frm, &ni->ni_rates); if (capinfo & IEEE80211_CAPINFO_SPECTRUM_MGMT) { frm = ieee80211_add_powercapability(frm, ic->ic_curchan); frm = ieee80211_add_supportedchannels(frm, ic); } /* * Check the channel - we may be using an 11n NIC with an * 11n capable station, but we're configured to be an 11b * channel. */ if ((vap->iv_flags_ht & IEEE80211_FHT_HT) && IEEE80211_IS_CHAN_HT(ni->ni_chan) && ni->ni_ies.htcap_ie != NULL && ni->ni_ies.htcap_ie[0] == IEEE80211_ELEMID_HTCAP) { frm = ieee80211_add_htcap(frm, ni); } frm = ieee80211_add_wpa(frm, vap); if ((ic->ic_flags & IEEE80211_F_WME) && ni->ni_ies.wme_ie != NULL) frm = ieee80211_add_wme_info(frm, &ic->ic_wme); /* * Same deal - only send HT info if we're on an 11n * capable channel. */ if ((vap->iv_flags_ht & IEEE80211_FHT_HT) && IEEE80211_IS_CHAN_HT(ni->ni_chan) && ni->ni_ies.htcap_ie != NULL && ni->ni_ies.htcap_ie[0] == IEEE80211_ELEMID_VENDOR) { frm = ieee80211_add_htcap_vendor(frm, ni); } #ifdef IEEE80211_SUPPORT_SUPERG if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS)) { frm = ieee80211_add_ath(frm, IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS), ((vap->iv_flags & IEEE80211_F_WPA) == 0 && ni->ni_authmode != IEEE80211_AUTH_8021X) ? vap->iv_def_txkey : IEEE80211_KEYIX_NONE); } #endif /* IEEE80211_SUPPORT_SUPERG */ if (vap->iv_appie_assocreq != NULL) frm = add_appie(frm, vap->iv_appie_assocreq); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); ieee80211_add_callback(m, ieee80211_tx_mgt_cb, (void *) vap->iv_state); break; case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: /* * asresp frame format * [2] capability information * [2] status * [2] association ID * [tlv] supported rates * [tlv] extended supported rates * [tlv] HT capabilities (standard, if STA enabled) * [tlv] HT information (standard, if STA enabled) * [tlv] WME (if configured and STA enabled) * [tlv] HT capabilities (vendor OUI, if STA enabled) * [tlv] HT information (vendor OUI, if STA enabled) * [tlv] Atheros capabilities (if STA enabled) * [tlv] AppIE's (optional) */ m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + 2 + IEEE80211_RATE_SIZE + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + sizeof(struct ieee80211_ie_htcap) + 4 + sizeof(struct ieee80211_ie_htinfo) + 4 + sizeof(struct ieee80211_wme_param) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) #endif + (vap->iv_appie_assocresp != NULL ? vap->iv_appie_assocresp->ie_len : 0) ); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); capinfo = ieee80211_getcapinfo(vap, bss->ni_chan); *(uint16_t *)frm = htole16(capinfo); frm += 2; *(uint16_t *)frm = htole16(arg); /* status */ frm += 2; if (arg == IEEE80211_STATUS_SUCCESS) { *(uint16_t *)frm = htole16(ni->ni_associd); IEEE80211_NODE_STAT(ni, tx_assoc); } else IEEE80211_NODE_STAT(ni, tx_assoc_fail); frm += 2; frm = ieee80211_add_rates(frm, &ni->ni_rates); frm = ieee80211_add_xrates(frm, &ni->ni_rates); /* NB: respond according to what we received */ if ((ni->ni_flags & HTFLAGS) == IEEE80211_NODE_HT) { frm = ieee80211_add_htcap(frm, ni); frm = ieee80211_add_htinfo(frm, ni); } if ((vap->iv_flags & IEEE80211_F_WME) && ni->ni_ies.wme_ie != NULL) frm = ieee80211_add_wme_param(frm, &ic->ic_wme); if ((ni->ni_flags & HTFLAGS) == HTFLAGS) { frm = ieee80211_add_htcap_vendor(frm, ni); frm = ieee80211_add_htinfo_vendor(frm, ni); } #ifdef IEEE80211_SUPPORT_SUPERG if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS)) frm = ieee80211_add_ath(frm, IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS), ((vap->iv_flags & IEEE80211_F_WPA) == 0 && ni->ni_authmode != IEEE80211_AUTH_8021X) ? vap->iv_def_txkey : IEEE80211_KEYIX_NONE); #endif /* IEEE80211_SUPPORT_SUPERG */ if (vap->iv_appie_assocresp != NULL) frm = add_appie(frm, vap->iv_appie_assocresp); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); break; case IEEE80211_FC0_SUBTYPE_DISASSOC: IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, "send station disassociate (reason %d)", arg); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t)); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); *(uint16_t *)frm = htole16(arg); /* reason */ m->m_pkthdr.len = m->m_len = sizeof(uint16_t); IEEE80211_NODE_STAT(ni, tx_disassoc); IEEE80211_NODE_STAT_SET(ni, tx_disassoc_code, arg); break; default: IEEE80211_NOTE(vap, IEEE80211_MSG_ANY, ni, "invalid mgmt frame type %u", type); senderr(EINVAL, is_tx_unknownmgt); /* NOTREACHED */ } /* NB: force non-ProbeResp frames to the highest queue */ params.ibp_pri = WME_AC_VO; params.ibp_rate0 = bss->ni_txparms->mgmtrate; /* NB: we know all frames are unicast */ params.ibp_try0 = bss->ni_txparms->maxretry; params.ibp_power = bss->ni_txpower; return ieee80211_mgmt_output(ni, m, type, ¶ms); bad: ieee80211_free_node(ni); return ret; #undef senderr #undef HTFLAGS } /* * Return an mbuf with a probe response frame in it. * Space is left to prepend and 802.11 header at the * front but it's left to the caller to fill in. */ struct mbuf * ieee80211_alloc_proberesp(struct ieee80211_node *bss, int legacy) { struct ieee80211vap *vap = bss->ni_vap; struct ieee80211com *ic = bss->ni_ic; const struct ieee80211_rateset *rs; struct mbuf *m; uint16_t capinfo; uint8_t *frm; /* * probe response frame format * [8] time stamp * [2] beacon interval * [2] cabability information * [tlv] ssid * [tlv] supported rates * [tlv] parameter set (FH/DS) * [tlv] parameter set (IBSS) * [tlv] country (optional) * [3] power control (optional) * [5] channel switch announcement (CSA) (optional) * [tlv] extended rate phy (ERP) * [tlv] extended supported rates * [tlv] RSN (optional) * [tlv] HT capabilities * [tlv] HT information * [tlv] WPA (optional) * [tlv] WME (optional) * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Vendor OUI HT information (optional) * [tlv] Atheros capabilities * [tlv] AppIE's (optional) * [tlv] Mesh ID (MBSS) * [tlv] Mesh Conf (MBSS) */ m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), 8 + sizeof(uint16_t) + sizeof(uint16_t) + 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + 7 /* max(7,3) */ + IEEE80211_COUNTRY_MAX_SIZE + 3 + sizeof(struct ieee80211_csa_ie) + sizeof(struct ieee80211_quiet_ie) + 3 + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + sizeof(struct ieee80211_ie_wpa) + sizeof(struct ieee80211_ie_htcap) + sizeof(struct ieee80211_ie_htinfo) + sizeof(struct ieee80211_ie_wpa) + sizeof(struct ieee80211_wme_param) + 4 + sizeof(struct ieee80211_ie_htcap) + 4 + sizeof(struct ieee80211_ie_htinfo) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) #endif #ifdef IEEE80211_SUPPORT_MESH + 2 + IEEE80211_MESHID_LEN + sizeof(struct ieee80211_meshconf_ie) #endif + (vap->iv_appie_proberesp != NULL ? vap->iv_appie_proberesp->ie_len : 0) ); if (m == NULL) { vap->iv_stats.is_tx_nobuf++; return NULL; } memset(frm, 0, 8); /* timestamp should be filled later */ frm += 8; *(uint16_t *)frm = htole16(bss->ni_intval); frm += 2; capinfo = ieee80211_getcapinfo(vap, bss->ni_chan); *(uint16_t *)frm = htole16(capinfo); frm += 2; frm = ieee80211_add_ssid(frm, bss->ni_essid, bss->ni_esslen); rs = ieee80211_get_suprates(ic, bss->ni_chan); frm = ieee80211_add_rates(frm, rs); if (IEEE80211_IS_CHAN_FHSS(bss->ni_chan)) { *frm++ = IEEE80211_ELEMID_FHPARMS; *frm++ = 5; *frm++ = bss->ni_fhdwell & 0x00ff; *frm++ = (bss->ni_fhdwell >> 8) & 0x00ff; *frm++ = IEEE80211_FH_CHANSET( ieee80211_chan2ieee(ic, bss->ni_chan)); *frm++ = IEEE80211_FH_CHANPAT( ieee80211_chan2ieee(ic, bss->ni_chan)); *frm++ = bss->ni_fhindex; } else { *frm++ = IEEE80211_ELEMID_DSPARMS; *frm++ = 1; *frm++ = ieee80211_chan2ieee(ic, bss->ni_chan); } if (vap->iv_opmode == IEEE80211_M_IBSS) { *frm++ = IEEE80211_ELEMID_IBSSPARMS; *frm++ = 2; *frm++ = 0; *frm++ = 0; /* TODO: ATIM window */ } if ((vap->iv_flags & IEEE80211_F_DOTH) || (vap->iv_flags_ext & IEEE80211_FEXT_DOTD)) frm = ieee80211_add_countryie(frm, ic); if (vap->iv_flags & IEEE80211_F_DOTH) { if (IEEE80211_IS_CHAN_5GHZ(bss->ni_chan)) frm = ieee80211_add_powerconstraint(frm, vap); if (ic->ic_flags & IEEE80211_F_CSAPENDING) frm = ieee80211_add_csa(frm, vap); } if (vap->iv_flags & IEEE80211_F_DOTH) { if (IEEE80211_IS_CHAN_DFS(ic->ic_bsschan) && (vap->iv_flags_ext & IEEE80211_FEXT_DFS)) { if (vap->iv_quiet) frm = ieee80211_add_quiet(frm, vap); } } if (IEEE80211_IS_CHAN_ANYG(bss->ni_chan)) frm = ieee80211_add_erp(frm, ic); frm = ieee80211_add_xrates(frm, rs); frm = ieee80211_add_rsn(frm, vap); /* * NB: legacy 11b clients do not get certain ie's. * The caller identifies such clients by passing * a token in legacy to us. Could expand this to be * any legacy client for stuff like HT ie's. */ if (IEEE80211_IS_CHAN_HT(bss->ni_chan) && legacy != IEEE80211_SEND_LEGACY_11B) { frm = ieee80211_add_htcap(frm, bss); frm = ieee80211_add_htinfo(frm, bss); } frm = ieee80211_add_wpa(frm, vap); if (vap->iv_flags & IEEE80211_F_WME) frm = ieee80211_add_wme_param(frm, &ic->ic_wme); if (IEEE80211_IS_CHAN_HT(bss->ni_chan) && (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) && legacy != IEEE80211_SEND_LEGACY_11B) { frm = ieee80211_add_htcap_vendor(frm, bss); frm = ieee80211_add_htinfo_vendor(frm, bss); } #ifdef IEEE80211_SUPPORT_SUPERG if ((vap->iv_flags & IEEE80211_F_ATHEROS) && legacy != IEEE80211_SEND_LEGACY_11B) frm = ieee80211_add_athcaps(frm, bss); #endif if (vap->iv_appie_proberesp != NULL) frm = add_appie(frm, vap->iv_appie_proberesp); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) { frm = ieee80211_add_meshid(frm, vap); frm = ieee80211_add_meshconf(frm, vap); } #endif m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return m; } /* * Send a probe response frame to the specified mac address. * This does not go through the normal mgt frame api so we * can specify the destination address and re-use the bss node * for the sta reference. */ int ieee80211_send_proberesp(struct ieee80211vap *vap, const uint8_t da[IEEE80211_ADDR_LEN], int legacy) { struct ieee80211_node *bss = vap->iv_bss; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_frame *wh; struct mbuf *m; int ret; if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT, bss, "block %s frame in CAC state", "probe response"); vap->iv_stats.is_tx_badstate++; return EIO; /* XXX */ } /* * Hold a reference on the node so it doesn't go away until after * the xmit is complete all the way in the driver. On error we * will remove our reference. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, bss, ether_sprintf(bss->ni_macaddr), ieee80211_node_refcnt(bss)+1); ieee80211_ref_node(bss); m = ieee80211_alloc_proberesp(bss, legacy); if (m == NULL) { ieee80211_free_node(bss); return ENOMEM; } M_PREPEND(m, sizeof(struct ieee80211_frame), M_NOWAIT); KASSERT(m != NULL, ("no room for header")); IEEE80211_TX_LOCK(ic); wh = mtod(m, struct ieee80211_frame *); ieee80211_send_setup(bss, m, IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_RESP, IEEE80211_NONQOS_TID, vap->iv_myaddr, da, bss->ni_bssid); /* XXX power management? */ m->m_flags |= M_ENCAP; /* mark encapsulated */ M_WME_SETAC(m, WME_AC_BE); IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DUMPPKTS, "send probe resp on channel %u to %s%s\n", ieee80211_chan2ieee(ic, ic->ic_curchan), ether_sprintf(da), legacy ? " " : ""); IEEE80211_NODE_STAT(bss, tx_mgmt); ret = ieee80211_raw_output(vap, bss, m, NULL); IEEE80211_TX_UNLOCK(ic); return (ret); } /* * Allocate and build a RTS (Request To Send) control frame. */ struct mbuf * ieee80211_alloc_rts(struct ieee80211com *ic, const uint8_t ra[IEEE80211_ADDR_LEN], const uint8_t ta[IEEE80211_ADDR_LEN], uint16_t dur) { struct ieee80211_frame_rts *rts; struct mbuf *m; /* XXX honor ic_headroom */ m = m_gethdr(M_NOWAIT, MT_DATA); if (m != NULL) { rts = mtod(m, struct ieee80211_frame_rts *); rts->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_RTS; rts->i_fc[1] = IEEE80211_FC1_DIR_NODS; *(u_int16_t *)rts->i_dur = htole16(dur); IEEE80211_ADDR_COPY(rts->i_ra, ra); IEEE80211_ADDR_COPY(rts->i_ta, ta); m->m_pkthdr.len = m->m_len = sizeof(struct ieee80211_frame_rts); } return m; } /* * Allocate and build a CTS (Clear To Send) control frame. */ struct mbuf * ieee80211_alloc_cts(struct ieee80211com *ic, const uint8_t ra[IEEE80211_ADDR_LEN], uint16_t dur) { struct ieee80211_frame_cts *cts; struct mbuf *m; /* XXX honor ic_headroom */ m = m_gethdr(M_NOWAIT, MT_DATA); if (m != NULL) { cts = mtod(m, struct ieee80211_frame_cts *); cts->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_CTS; cts->i_fc[1] = IEEE80211_FC1_DIR_NODS; *(u_int16_t *)cts->i_dur = htole16(dur); IEEE80211_ADDR_COPY(cts->i_ra, ra); m->m_pkthdr.len = m->m_len = sizeof(struct ieee80211_frame_cts); } return m; } static void ieee80211_tx_mgt_timeout(void *arg) { struct ieee80211vap *vap = arg; IEEE80211_LOCK(vap->iv_ic); if (vap->iv_state != IEEE80211_S_INIT && (vap->iv_ic->ic_flags & IEEE80211_F_SCAN) == 0) { /* * NB: it's safe to specify a timeout as the reason here; * it'll only be used in the right state. */ ieee80211_new_state_locked(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_TIMEOUT); } IEEE80211_UNLOCK(vap->iv_ic); } /* * This is the callback set on net80211-sourced transmitted * authentication request frames. * * This does a couple of things: * * + If the frame transmitted was a success, it schedules a future * event which will transition the interface to scan. * If a state transition _then_ occurs before that event occurs, * said state transition will cancel this callout. * * + If the frame transmit was a failure, it immediately schedules * the transition back to scan. */ static void ieee80211_tx_mgt_cb(struct ieee80211_node *ni, void *arg, int status) { struct ieee80211vap *vap = ni->ni_vap; enum ieee80211_state ostate = (enum ieee80211_state) arg; /* * Frame transmit completed; arrange timer callback. If * transmit was successfuly we wait for response. Otherwise * we arrange an immediate callback instead of doing the * callback directly since we don't know what state the driver * is in (e.g. what locks it is holding). This work should * not be too time-critical and not happen too often so the * added overhead is acceptable. * * XXX what happens if !acked but response shows up before callback? */ if (vap->iv_state == ostate) { callout_reset(&vap->iv_mgtsend, status == 0 ? IEEE80211_TRANS_WAIT*hz : 0, ieee80211_tx_mgt_timeout, vap); } } static void ieee80211_beacon_construct(struct mbuf *m, uint8_t *frm, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_rateset *rs = &ni->ni_rates; uint16_t capinfo; /* * beacon frame format * [8] time stamp * [2] beacon interval * [2] cabability information * [tlv] ssid * [tlv] supported rates * [3] parameter set (DS) * [8] CF parameter set (optional) * [tlv] parameter set (IBSS/TIM) * [tlv] country (optional) * [3] power control (optional) * [5] channel switch announcement (CSA) (optional) * [tlv] extended rate phy (ERP) * [tlv] extended supported rates * [tlv] RSN parameters * [tlv] HT capabilities * [tlv] HT information * XXX Vendor-specific OIDs (e.g. Atheros) * [tlv] WPA parameters * [tlv] WME parameters * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Vendor OUI HT information (optional) * [tlv] Atheros capabilities (optional) * [tlv] TDMA parameters (optional) * [tlv] Mesh ID (MBSS) * [tlv] Mesh Conf (MBSS) * [tlv] application data (optional) */ memset(bo, 0, sizeof(*bo)); memset(frm, 0, 8); /* XXX timestamp is set by hardware/driver */ frm += 8; *(uint16_t *)frm = htole16(ni->ni_intval); frm += 2; capinfo = ieee80211_getcapinfo(vap, ni->ni_chan); bo->bo_caps = (uint16_t *)frm; *(uint16_t *)frm = htole16(capinfo); frm += 2; *frm++ = IEEE80211_ELEMID_SSID; if ((vap->iv_flags & IEEE80211_F_HIDESSID) == 0) { *frm++ = ni->ni_esslen; memcpy(frm, ni->ni_essid, ni->ni_esslen); frm += ni->ni_esslen; } else *frm++ = 0; frm = ieee80211_add_rates(frm, rs); if (!IEEE80211_IS_CHAN_FHSS(ni->ni_chan)) { *frm++ = IEEE80211_ELEMID_DSPARMS; *frm++ = 1; *frm++ = ieee80211_chan2ieee(ic, ni->ni_chan); } if (ic->ic_flags & IEEE80211_F_PCF) { bo->bo_cfp = frm; frm = ieee80211_add_cfparms(frm, ic); } bo->bo_tim = frm; if (vap->iv_opmode == IEEE80211_M_IBSS) { *frm++ = IEEE80211_ELEMID_IBSSPARMS; *frm++ = 2; *frm++ = 0; *frm++ = 0; /* TODO: ATIM window */ bo->bo_tim_len = 0; } else if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_MBSS) { /* TIM IE is the same for Mesh and Hostap */ struct ieee80211_tim_ie *tie = (struct ieee80211_tim_ie *) frm; tie->tim_ie = IEEE80211_ELEMID_TIM; tie->tim_len = 4; /* length */ tie->tim_count = 0; /* DTIM count */ tie->tim_period = vap->iv_dtim_period; /* DTIM period */ tie->tim_bitctl = 0; /* bitmap control */ tie->tim_bitmap[0] = 0; /* Partial Virtual Bitmap */ frm += sizeof(struct ieee80211_tim_ie); bo->bo_tim_len = 1; } bo->bo_tim_trailer = frm; if ((vap->iv_flags & IEEE80211_F_DOTH) || (vap->iv_flags_ext & IEEE80211_FEXT_DOTD)) frm = ieee80211_add_countryie(frm, ic); if (vap->iv_flags & IEEE80211_F_DOTH) { if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) frm = ieee80211_add_powerconstraint(frm, vap); bo->bo_csa = frm; if (ic->ic_flags & IEEE80211_F_CSAPENDING) frm = ieee80211_add_csa(frm, vap); } else bo->bo_csa = frm; if (vap->iv_flags & IEEE80211_F_DOTH) { bo->bo_quiet = frm; if (IEEE80211_IS_CHAN_DFS(ic->ic_bsschan) && (vap->iv_flags_ext & IEEE80211_FEXT_DFS)) { if (vap->iv_quiet) frm = ieee80211_add_quiet(frm,vap); } } else bo->bo_quiet = frm; if (IEEE80211_IS_CHAN_ANYG(ni->ni_chan)) { bo->bo_erp = frm; frm = ieee80211_add_erp(frm, ic); } frm = ieee80211_add_xrates(frm, rs); frm = ieee80211_add_rsn(frm, vap); if (IEEE80211_IS_CHAN_HT(ni->ni_chan)) { frm = ieee80211_add_htcap(frm, ni); bo->bo_htinfo = frm; frm = ieee80211_add_htinfo(frm, ni); } frm = ieee80211_add_wpa(frm, vap); if (vap->iv_flags & IEEE80211_F_WME) { bo->bo_wme = frm; frm = ieee80211_add_wme_param(frm, &ic->ic_wme); } if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT)) { frm = ieee80211_add_htcap_vendor(frm, ni); frm = ieee80211_add_htinfo_vendor(frm, ni); } #ifdef IEEE80211_SUPPORT_SUPERG if (vap->iv_flags & IEEE80211_F_ATHEROS) { bo->bo_ath = frm; frm = ieee80211_add_athcaps(frm, ni); } #endif #ifdef IEEE80211_SUPPORT_TDMA if (vap->iv_caps & IEEE80211_C_TDMA) { bo->bo_tdma = frm; frm = ieee80211_add_tdma(frm, vap); } #endif if (vap->iv_appie_beacon != NULL) { bo->bo_appie = frm; bo->bo_appie_len = vap->iv_appie_beacon->ie_len; frm = add_appie(frm, vap->iv_appie_beacon); } #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) { frm = ieee80211_add_meshid(frm, vap); bo->bo_meshconf = frm; frm = ieee80211_add_meshconf(frm, vap); } #endif bo->bo_tim_trailer_len = frm - bo->bo_tim_trailer; bo->bo_csa_trailer_len = frm - bo->bo_csa; m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); } /* * Allocate a beacon frame and fillin the appropriate bits. */ struct mbuf * ieee80211_beacon_alloc(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = vap->iv_ifp; struct ieee80211_frame *wh; struct mbuf *m; int pktlen; uint8_t *frm; /* * beacon frame format * [8] time stamp * [2] beacon interval * [2] cabability information * [tlv] ssid * [tlv] supported rates * [3] parameter set (DS) * [8] CF parameter set (optional) * [tlv] parameter set (IBSS/TIM) * [tlv] country (optional) * [3] power control (optional) * [5] channel switch announcement (CSA) (optional) * [tlv] extended rate phy (ERP) * [tlv] extended supported rates * [tlv] RSN parameters * [tlv] HT capabilities * [tlv] HT information * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Vendor OUI HT information (optional) * XXX Vendor-specific OIDs (e.g. Atheros) * [tlv] WPA parameters * [tlv] WME parameters * [tlv] TDMA parameters (optional) * [tlv] Mesh ID (MBSS) * [tlv] Mesh Conf (MBSS) * [tlv] application data (optional) * NB: we allocate the max space required for the TIM bitmap. * XXX how big is this? */ pktlen = 8 /* time stamp */ + sizeof(uint16_t) /* beacon interval */ + sizeof(uint16_t) /* capabilities */ + 2 + ni->ni_esslen /* ssid */ + 2 + IEEE80211_RATE_SIZE /* supported rates */ + 2 + 1 /* DS parameters */ + 2 + 6 /* CF parameters */ + 2 + 4 + vap->iv_tim_len /* DTIM/IBSSPARMS */ + IEEE80211_COUNTRY_MAX_SIZE /* country */ + 2 + 1 /* power control */ + sizeof(struct ieee80211_csa_ie) /* CSA */ + sizeof(struct ieee80211_quiet_ie) /* Quiet */ + 2 + 1 /* ERP */ + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + (vap->iv_caps & IEEE80211_C_WPA ? /* WPA 1+2 */ 2*sizeof(struct ieee80211_ie_wpa) : 0) /* XXX conditional? */ + 4+2*sizeof(struct ieee80211_ie_htcap)/* HT caps */ + 4+2*sizeof(struct ieee80211_ie_htinfo)/* HT info */ + (vap->iv_caps & IEEE80211_C_WME ? /* WME */ sizeof(struct ieee80211_wme_param) : 0) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) /* ATH */ #endif #ifdef IEEE80211_SUPPORT_TDMA + (vap->iv_caps & IEEE80211_C_TDMA ? /* TDMA */ sizeof(struct ieee80211_tdma_param) : 0) #endif #ifdef IEEE80211_SUPPORT_MESH + 2 + ni->ni_meshidlen + sizeof(struct ieee80211_meshconf_ie) #endif + IEEE80211_MAX_APPIE ; m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), pktlen); if (m == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_ANY, "%s: cannot get buf; size %u\n", __func__, pktlen); vap->iv_stats.is_tx_nobuf++; return NULL; } ieee80211_beacon_construct(m, frm, ni); M_PREPEND(m, sizeof(struct ieee80211_frame), M_NOWAIT); KASSERT(m != NULL, ("no space for 802.11 header?")); wh = mtod(m, struct ieee80211_frame *); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_BEACON; wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; *(uint16_t *)wh->i_dur = 0; IEEE80211_ADDR_COPY(wh->i_addr1, ifp->if_broadcastaddr); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, ni->ni_bssid); *(uint16_t *)wh->i_seq = 0; return m; } /* * Update the dynamic parts of a beacon frame based on the current state. */ int ieee80211_beacon_update(struct ieee80211_node *ni, struct mbuf *m, int mcast) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off; struct ieee80211com *ic = ni->ni_ic; int len_changed = 0; uint16_t capinfo; struct ieee80211_frame *wh; ieee80211_seq seqno; IEEE80211_LOCK(ic); /* * Handle 11h channel change when we've reached the count. * We must recalculate the beacon frame contents to account * for the new channel. Note we do this only for the first * vap that reaches this point; subsequent vaps just update * their beacon state to reflect the recalculated channel. */ if (isset(bo->bo_flags, IEEE80211_BEACON_CSA) && vap->iv_csa_count == ic->ic_csa_count) { vap->iv_csa_count = 0; /* * Effect channel change before reconstructing the beacon * frame contents as many places reference ni_chan. */ if (ic->ic_csa_newchan != NULL) ieee80211_csa_completeswitch(ic); /* * NB: ieee80211_beacon_construct clears all pending * updates in bo_flags so we don't need to explicitly * clear IEEE80211_BEACON_CSA. */ ieee80211_beacon_construct(m, mtod(m, uint8_t*) + sizeof(struct ieee80211_frame), ni); /* XXX do WME aggressive mode processing? */ IEEE80211_UNLOCK(ic); return 1; /* just assume length changed */ } wh = mtod(m, struct ieee80211_frame *); seqno = ni->ni_txseqs[IEEE80211_NONQOS_TID]++; *(uint16_t *)&wh->i_seq[0] = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); M_SEQNO_SET(m, seqno); /* XXX faster to recalculate entirely or just changes? */ capinfo = ieee80211_getcapinfo(vap, ni->ni_chan); *bo->bo_caps = htole16(capinfo); if (vap->iv_flags & IEEE80211_F_WME) { struct ieee80211_wme_state *wme = &ic->ic_wme; /* * Check for agressive mode change. When there is * significant high priority traffic in the BSS * throttle back BE traffic by using conservative * parameters. Otherwise BE uses agressive params * to optimize performance of legacy/non-QoS traffic. */ if (wme->wme_flags & WME_F_AGGRMODE) { if (wme->wme_hipri_traffic > wme->wme_hipri_switch_thresh) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME, "%s: traffic %u, disable aggressive mode\n", __func__, wme->wme_hipri_traffic); wme->wme_flags &= ~WME_F_AGGRMODE; ieee80211_wme_updateparams_locked(vap); wme->wme_hipri_traffic = wme->wme_hipri_switch_hysteresis; } else wme->wme_hipri_traffic = 0; } else { if (wme->wme_hipri_traffic <= wme->wme_hipri_switch_thresh) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME, "%s: traffic %u, enable aggressive mode\n", __func__, wme->wme_hipri_traffic); wme->wme_flags |= WME_F_AGGRMODE; ieee80211_wme_updateparams_locked(vap); wme->wme_hipri_traffic = 0; } else wme->wme_hipri_traffic = wme->wme_hipri_switch_hysteresis; } if (isset(bo->bo_flags, IEEE80211_BEACON_WME)) { (void) ieee80211_add_wme_param(bo->bo_wme, wme); clrbit(bo->bo_flags, IEEE80211_BEACON_WME); } } if (isset(bo->bo_flags, IEEE80211_BEACON_HTINFO)) { ieee80211_ht_update_beacon(vap, bo); clrbit(bo->bo_flags, IEEE80211_BEACON_HTINFO); } #ifdef IEEE80211_SUPPORT_TDMA if (vap->iv_caps & IEEE80211_C_TDMA) { /* * NB: the beacon is potentially updated every TBTT. */ ieee80211_tdma_update_beacon(vap, bo); } #endif #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_update_beacon(vap, bo); #endif if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_MBSS) { /* NB: no IBSS support*/ struct ieee80211_tim_ie *tie = (struct ieee80211_tim_ie *) bo->bo_tim; if (isset(bo->bo_flags, IEEE80211_BEACON_TIM)) { u_int timlen, timoff, i; /* * ATIM/DTIM needs updating. If it fits in the * current space allocated then just copy in the * new bits. Otherwise we need to move any trailing * data to make room. Note that we know there is * contiguous space because ieee80211_beacon_allocate * insures there is space in the mbuf to write a * maximal-size virtual bitmap (based on iv_max_aid). */ /* * Calculate the bitmap size and offset, copy any * trailer out of the way, and then copy in the * new bitmap and update the information element. * Note that the tim bitmap must contain at least * one byte and any offset must be even. */ if (vap->iv_ps_pending != 0) { timoff = 128; /* impossibly large */ for (i = 0; i < vap->iv_tim_len; i++) if (vap->iv_tim_bitmap[i]) { timoff = i &~ 1; break; } KASSERT(timoff != 128, ("tim bitmap empty!")); for (i = vap->iv_tim_len-1; i >= timoff; i--) if (vap->iv_tim_bitmap[i]) break; timlen = 1 + (i - timoff); } else { timoff = 0; timlen = 1; } if (timlen != bo->bo_tim_len) { /* copy up/down trailer */ int adjust = tie->tim_bitmap+timlen - bo->bo_tim_trailer; ovbcopy(bo->bo_tim_trailer, bo->bo_tim_trailer+adjust, bo->bo_tim_trailer_len); bo->bo_tim_trailer += adjust; bo->bo_erp += adjust; bo->bo_htinfo += adjust; #ifdef IEEE80211_SUPPORT_SUPERG bo->bo_ath += adjust; #endif #ifdef IEEE80211_SUPPORT_TDMA bo->bo_tdma += adjust; #endif #ifdef IEEE80211_SUPPORT_MESH bo->bo_meshconf += adjust; #endif bo->bo_appie += adjust; bo->bo_wme += adjust; bo->bo_csa += adjust; bo->bo_quiet += adjust; bo->bo_tim_len = timlen; /* update information element */ tie->tim_len = 3 + timlen; tie->tim_bitctl = timoff; len_changed = 1; } memcpy(tie->tim_bitmap, vap->iv_tim_bitmap + timoff, bo->bo_tim_len); clrbit(bo->bo_flags, IEEE80211_BEACON_TIM); IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, "%s: TIM updated, pending %u, off %u, len %u\n", __func__, vap->iv_ps_pending, timoff, timlen); } /* count down DTIM period */ if (tie->tim_count == 0) tie->tim_count = tie->tim_period - 1; else tie->tim_count--; /* update state for buffered multicast frames on DTIM */ if (mcast && tie->tim_count == 0) tie->tim_bitctl |= 1; else tie->tim_bitctl &= ~1; if (isset(bo->bo_flags, IEEE80211_BEACON_CSA)) { struct ieee80211_csa_ie *csa = (struct ieee80211_csa_ie *) bo->bo_csa; /* * Insert or update CSA ie. If we're just starting * to count down to the channel switch then we need * to insert the CSA ie. Otherwise we just need to * drop the count. The actual change happens above * when the vap's count reaches the target count. */ if (vap->iv_csa_count == 0) { memmove(&csa[1], csa, bo->bo_csa_trailer_len); bo->bo_erp += sizeof(*csa); bo->bo_htinfo += sizeof(*csa); bo->bo_wme += sizeof(*csa); #ifdef IEEE80211_SUPPORT_SUPERG bo->bo_ath += sizeof(*csa); #endif #ifdef IEEE80211_SUPPORT_TDMA bo->bo_tdma += sizeof(*csa); #endif #ifdef IEEE80211_SUPPORT_MESH bo->bo_meshconf += sizeof(*csa); #endif bo->bo_appie += sizeof(*csa); bo->bo_csa_trailer_len += sizeof(*csa); bo->bo_quiet += sizeof(*csa); bo->bo_tim_trailer_len += sizeof(*csa); m->m_len += sizeof(*csa); m->m_pkthdr.len += sizeof(*csa); ieee80211_add_csa(bo->bo_csa, vap); } else csa->csa_count--; vap->iv_csa_count++; /* NB: don't clear IEEE80211_BEACON_CSA */ } if (IEEE80211_IS_CHAN_DFS(ic->ic_bsschan) && (vap->iv_flags_ext & IEEE80211_FEXT_DFS) ){ if (vap->iv_quiet) ieee80211_add_quiet(bo->bo_quiet, vap); } if (isset(bo->bo_flags, IEEE80211_BEACON_ERP)) { /* * ERP element needs updating. */ (void) ieee80211_add_erp(bo->bo_erp, ic); clrbit(bo->bo_flags, IEEE80211_BEACON_ERP); } #ifdef IEEE80211_SUPPORT_SUPERG if (isset(bo->bo_flags, IEEE80211_BEACON_ATH)) { ieee80211_add_athcaps(bo->bo_ath, ni); clrbit(bo->bo_flags, IEEE80211_BEACON_ATH); } #endif } if (isset(bo->bo_flags, IEEE80211_BEACON_APPIE)) { const struct ieee80211_appie *aie = vap->iv_appie_beacon; int aielen; uint8_t *frm; aielen = 0; if (aie != NULL) aielen += aie->ie_len; if (aielen != bo->bo_appie_len) { /* copy up/down trailer */ int adjust = aielen - bo->bo_appie_len; ovbcopy(bo->bo_tim_trailer, bo->bo_tim_trailer+adjust, bo->bo_tim_trailer_len); bo->bo_tim_trailer += adjust; bo->bo_appie += adjust; bo->bo_appie_len = aielen; len_changed = 1; } frm = bo->bo_appie; if (aie != NULL) frm = add_appie(frm, aie); clrbit(bo->bo_flags, IEEE80211_BEACON_APPIE); } IEEE80211_UNLOCK(ic); return len_changed; } /* * Do Ethernet-LLC encapsulation for each payload in a fast frame * tunnel encapsulation. The frame is assumed to have an Ethernet * header at the front that must be stripped before prepending the * LLC followed by the Ethernet header passed in (with an Ethernet * type that specifies the payload size). */ struct mbuf * ieee80211_ff_encap1(struct ieee80211vap *vap, struct mbuf *m, const struct ether_header *eh) { struct llc *llc; uint16_t payload; /* XXX optimize by combining m_adj+M_PREPEND */ m_adj(m, sizeof(struct ether_header) - sizeof(struct llc)); llc = mtod(m, struct llc *); llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP; llc->llc_control = LLC_UI; llc->llc_snap.org_code[0] = 0; llc->llc_snap.org_code[1] = 0; llc->llc_snap.org_code[2] = 0; llc->llc_snap.ether_type = eh->ether_type; payload = m->m_pkthdr.len; /* NB: w/o Ethernet header */ M_PREPEND(m, sizeof(struct ether_header), M_NOWAIT); if (m == NULL) { /* XXX cannot happen */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: no space for ether_header\n", __func__); vap->iv_stats.is_tx_nobuf++; return NULL; } ETHER_HEADER_COPY(mtod(m, void *), eh); mtod(m, struct ether_header *)->ether_type = htons(payload); return m; } /* * Complete an mbuf transmission. * * For now, this simply processes a completed frame after the * driver has completed it's transmission and/or retransmission. * It assumes the frame is an 802.11 encapsulated frame. * * Later on it will grow to become the exit path for a given frame * from the driver and, depending upon how it's been encapsulated * and already transmitted, it may end up doing A-MPDU retransmission, * power save requeuing, etc. * * In order for the above to work, the driver entry point to this * must not hold any driver locks. Thus, the driver needs to delay * any actual mbuf completion until it can release said locks. * * This frees the mbuf and if the mbuf has a node reference, * the node reference will be freed. */ void ieee80211_tx_complete(struct ieee80211_node *ni, struct mbuf *m, int status) { if (ni != NULL) { struct ifnet *ifp = ni->ni_vap->iv_ifp; if (status == 0) { if_inc_counter(ifp, IFCOUNTER_OBYTES, m->m_pkthdr.len); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); if (m->m_flags & M_MCAST) if_inc_counter(ifp, IFCOUNTER_OMCASTS, 1); } else if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (m->m_flags & M_TXCB) ieee80211_process_callback(ni, m, status); ieee80211_free_node(ni); } m_freem(m); } Index: projects/release-pkg/sys/net80211/ieee80211_phy.c =================================================================== --- projects/release-pkg/sys/net80211/ieee80211_phy.c (revision 297604) +++ projects/release-pkg/sys/net80211/ieee80211_phy.c (revision 297605) @@ -1,632 +1,632 @@ /*- * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting * 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$"); /* * IEEE 802.11 PHY-related support. */ #include "opt_inet.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef notyet struct ieee80211_ds_plcp_hdr { uint8_t i_signal; uint8_t i_service; uint16_t i_length; uint16_t i_crc; } __packed; #endif /* notyet */ /* shorthands to compact tables for readability */ #define OFDM IEEE80211_T_OFDM #define CCK IEEE80211_T_CCK #define TURBO IEEE80211_T_TURBO #define HALF IEEE80211_T_OFDM_HALF #define QUART IEEE80211_T_OFDM_QUARTER #define HT IEEE80211_T_HT /* XXX the 11n and the basic rate flag are unfortunately overlapping. Grr. */ #define N(r) (IEEE80211_RATE_MCS | r) #define PBCC (IEEE80211_T_OFDM_QUARTER+1) /* XXX */ #define B(r) (IEEE80211_RATE_BASIC | r) #define Mb(x) (x*1000) static struct ieee80211_rate_table ieee80211_11b_table = { .rateCount = 4, /* XXX no PBCC */ .info = { /* short ctrl */ /* Preamble dot11Rate Rate */ [0] = { .phy = CCK, 1000, 0x00, B(2), 0 },/* 1 Mb */ [1] = { .phy = CCK, 2000, 0x04, B(4), 1 },/* 2 Mb */ [2] = { .phy = CCK, 5500, 0x04, B(11), 1 },/* 5.5 Mb */ [3] = { .phy = CCK, 11000, 0x04, B(22), 1 },/* 11 Mb */ [4] = { .phy = PBCC, 22000, 0x04, 44, 3 } /* 22 Mb */ }, }; static struct ieee80211_rate_table ieee80211_11g_table = { .rateCount = 12, .info = { /* short ctrl */ /* Preamble dot11Rate Rate */ [0] = { .phy = CCK, 1000, 0x00, B(2), 0 }, [1] = { .phy = CCK, 2000, 0x04, B(4), 1 }, [2] = { .phy = CCK, 5500, 0x04, B(11), 2 }, [3] = { .phy = CCK, 11000, 0x04, B(22), 3 }, [4] = { .phy = OFDM, 6000, 0x00, 12, 4 }, [5] = { .phy = OFDM, 9000, 0x00, 18, 4 }, [6] = { .phy = OFDM, 12000, 0x00, 24, 6 }, [7] = { .phy = OFDM, 18000, 0x00, 36, 6 }, [8] = { .phy = OFDM, 24000, 0x00, 48, 8 }, [9] = { .phy = OFDM, 36000, 0x00, 72, 8 }, [10] = { .phy = OFDM, 48000, 0x00, 96, 8 }, [11] = { .phy = OFDM, 54000, 0x00, 108, 8 } }, }; static struct ieee80211_rate_table ieee80211_11a_table = { .rateCount = 8, .info = { /* short ctrl */ /* Preamble dot11Rate Rate */ [0] = { .phy = OFDM, 6000, 0x00, B(12), 0 }, [1] = { .phy = OFDM, 9000, 0x00, 18, 0 }, [2] = { .phy = OFDM, 12000, 0x00, B(24), 2 }, [3] = { .phy = OFDM, 18000, 0x00, 36, 2 }, [4] = { .phy = OFDM, 24000, 0x00, B(48), 4 }, [5] = { .phy = OFDM, 36000, 0x00, 72, 4 }, [6] = { .phy = OFDM, 48000, 0x00, 96, 4 }, [7] = { .phy = OFDM, 54000, 0x00, 108, 4 } }, }; static struct ieee80211_rate_table ieee80211_half_table = { .rateCount = 8, .info = { /* short ctrl */ /* Preamble dot11Rate Rate */ [0] = { .phy = HALF, 3000, 0x00, B(6), 0 }, [1] = { .phy = HALF, 4500, 0x00, 9, 0 }, [2] = { .phy = HALF, 6000, 0x00, B(12), 2 }, [3] = { .phy = HALF, 9000, 0x00, 18, 2 }, [4] = { .phy = HALF, 12000, 0x00, B(24), 4 }, [5] = { .phy = HALF, 18000, 0x00, 36, 4 }, [6] = { .phy = HALF, 24000, 0x00, 48, 4 }, [7] = { .phy = HALF, 27000, 0x00, 54, 4 } }, }; static struct ieee80211_rate_table ieee80211_quarter_table = { .rateCount = 8, .info = { /* short ctrl */ /* Preamble dot11Rate Rate */ [0] = { .phy = QUART, 1500, 0x00, B(3), 0 }, [1] = { .phy = QUART, 2250, 0x00, 4, 0 }, [2] = { .phy = QUART, 3000, 0x00, B(9), 2 }, [3] = { .phy = QUART, 4500, 0x00, 9, 2 }, [4] = { .phy = QUART, 6000, 0x00, B(12), 4 }, [5] = { .phy = QUART, 9000, 0x00, 18, 4 }, [6] = { .phy = QUART, 12000, 0x00, 24, 4 }, [7] = { .phy = QUART, 13500, 0x00, 27, 4 } }, }; static struct ieee80211_rate_table ieee80211_turbog_table = { .rateCount = 7, .info = { /* short ctrl */ /* Preamble dot11Rate Rate */ [0] = { .phy = TURBO, 12000, 0x00, B(12), 0 }, [1] = { .phy = TURBO, 24000, 0x00, B(24), 1 }, [2] = { .phy = TURBO, 36000, 0x00, 36, 1 }, [3] = { .phy = TURBO, 48000, 0x00, B(48), 3 }, [4] = { .phy = TURBO, 72000, 0x00, 72, 3 }, [5] = { .phy = TURBO, 96000, 0x00, 96, 3 }, [6] = { .phy = TURBO, 108000, 0x00, 108, 3 } }, }; static struct ieee80211_rate_table ieee80211_turboa_table = { .rateCount = 8, .info = { /* short ctrl */ /* Preamble dot11Rate Rate */ [0] = { .phy = TURBO, 12000, 0x00, B(12), 0 }, [1] = { .phy = TURBO, 18000, 0x00, 18, 0 }, [2] = { .phy = TURBO, 24000, 0x00, B(24), 2 }, [3] = { .phy = TURBO, 36000, 0x00, 36, 2 }, [4] = { .phy = TURBO, 48000, 0x00, B(48), 4 }, [5] = { .phy = TURBO, 72000, 0x00, 72, 4 }, [6] = { .phy = TURBO, 96000, 0x00, 96, 4 }, [7] = { .phy = TURBO, 108000, 0x00, 108, 4 } }, }; static struct ieee80211_rate_table ieee80211_11ng_table = { .rateCount = 36, .info = { /* short ctrl */ /* Preamble dot11Rate Rate */ [0] = { .phy = CCK, 1000, 0x00, B(2), 0 }, [1] = { .phy = CCK, 2000, 0x04, B(4), 1 }, [2] = { .phy = CCK, 5500, 0x04, B(11), 2 }, [3] = { .phy = CCK, 11000, 0x04, B(22), 3 }, [4] = { .phy = OFDM, 6000, 0x00, 12, 4 }, [5] = { .phy = OFDM, 9000, 0x00, 18, 4 }, [6] = { .phy = OFDM, 12000, 0x00, 24, 6 }, [7] = { .phy = OFDM, 18000, 0x00, 36, 6 }, [8] = { .phy = OFDM, 24000, 0x00, 48, 8 }, [9] = { .phy = OFDM, 36000, 0x00, 72, 8 }, [10] = { .phy = OFDM, 48000, 0x00, 96, 8 }, [11] = { .phy = OFDM, 54000, 0x00, 108, 8 }, [12] = { .phy = HT, 6500, 0x00, N(0), 4 }, [13] = { .phy = HT, 13000, 0x00, N(1), 6 }, [14] = { .phy = HT, 19500, 0x00, N(2), 6 }, [15] = { .phy = HT, 26000, 0x00, N(3), 8 }, [16] = { .phy = HT, 39000, 0x00, N(4), 8 }, [17] = { .phy = HT, 52000, 0x00, N(5), 8 }, [18] = { .phy = HT, 58500, 0x00, N(6), 8 }, [19] = { .phy = HT, 65000, 0x00, N(7), 8 }, [20] = { .phy = HT, 13000, 0x00, N(8), 4 }, [21] = { .phy = HT, 26000, 0x00, N(9), 6 }, [22] = { .phy = HT, 39000, 0x00, N(10), 6 }, [23] = { .phy = HT, 52000, 0x00, N(11), 8 }, [24] = { .phy = HT, 78000, 0x00, N(12), 8 }, [25] = { .phy = HT, 104000, 0x00, N(13), 8 }, [26] = { .phy = HT, 117000, 0x00, N(14), 8 }, [27] = { .phy = HT, 130000, 0x00, N(15), 8 }, [28] = { .phy = HT, 19500, 0x00, N(16), 4 }, [29] = { .phy = HT, 39000, 0x00, N(17), 6 }, [30] = { .phy = HT, 58500, 0x00, N(18), 6 }, [31] = { .phy = HT, 78000, 0x00, N(19), 8 }, [32] = { .phy = HT, 117000, 0x00, N(20), 8 }, [33] = { .phy = HT, 156000, 0x00, N(21), 8 }, [34] = { .phy = HT, 175500, 0x00, N(22), 8 }, [35] = { .phy = HT, 195000, 0x00, N(23), 8 }, }, }; static struct ieee80211_rate_table ieee80211_11na_table = { .rateCount = 32, .info = { /* short ctrl */ /* Preamble dot11Rate Rate */ [0] = { .phy = OFDM, 6000, 0x00, B(12), 0 }, [1] = { .phy = OFDM, 9000, 0x00, 18, 0 }, [2] = { .phy = OFDM, 12000, 0x00, B(24), 2 }, [3] = { .phy = OFDM, 18000, 0x00, 36, 2 }, [4] = { .phy = OFDM, 24000, 0x00, B(48), 4 }, [5] = { .phy = OFDM, 36000, 0x00, 72, 4 }, [6] = { .phy = OFDM, 48000, 0x00, 96, 4 }, [7] = { .phy = OFDM, 54000, 0x00, 108, 4 }, [8] = { .phy = HT, 6500, 0x00, N(0), 0 }, [9] = { .phy = HT, 13000, 0x00, N(1), 2 }, [10] = { .phy = HT, 19500, 0x00, N(2), 2 }, [11] = { .phy = HT, 26000, 0x00, N(3), 4 }, [12] = { .phy = HT, 39000, 0x00, N(4), 4 }, [13] = { .phy = HT, 52000, 0x00, N(5), 4 }, [14] = { .phy = HT, 58500, 0x00, N(6), 4 }, [15] = { .phy = HT, 65000, 0x00, N(7), 4 }, [16] = { .phy = HT, 13000, 0x00, N(8), 0 }, [17] = { .phy = HT, 26000, 0x00, N(9), 2 }, [18] = { .phy = HT, 39000, 0x00, N(10), 2 }, [19] = { .phy = HT, 52000, 0x00, N(11), 4 }, [20] = { .phy = HT, 78000, 0x00, N(12), 4 }, [21] = { .phy = HT, 104000, 0x00, N(13), 4 }, [22] = { .phy = HT, 117000, 0x00, N(14), 4 }, [23] = { .phy = HT, 130000, 0x00, N(15), 4 }, [24] = { .phy = HT, 19500, 0x00, N(16), 0 }, [25] = { .phy = HT, 39000, 0x00, N(17), 2 }, [26] = { .phy = HT, 58500, 0x00, N(18), 2 }, [27] = { .phy = HT, 78000, 0x00, N(19), 4 }, [28] = { .phy = HT, 117000, 0x00, N(20), 4 }, [29] = { .phy = HT, 156000, 0x00, N(21), 4 }, [30] = { .phy = HT, 175500, 0x00, N(22), 4 }, [31] = { .phy = HT, 195000, 0x00, N(23), 4 }, }, }; #undef Mb #undef B #undef OFDM #undef HALF #undef QUART #undef CCK #undef TURBO #undef XR #undef HT #undef N /* * Setup a rate table's reverse lookup table and fill in * ack durations. The reverse lookup tables are assumed * to be initialized to zero (or at least the first entry). * We use this as a key that indicates whether or not * we've previously setup the reverse lookup table. * * XXX not reentrant, but shouldn't matter */ static void ieee80211_setup_ratetable(struct ieee80211_rate_table *rt) { #define WLAN_CTRL_FRAME_SIZE \ (sizeof(struct ieee80211_frame_ack) + IEEE80211_CRC_LEN) int i; for (i = 0; i < nitems(rt->rateCodeToIndex); i++) rt->rateCodeToIndex[i] = (uint8_t) -1; for (i = 0; i < rt->rateCount; i++) { uint8_t code = rt->info[i].dot11Rate; uint8_t cix = rt->info[i].ctlRateIndex; uint8_t ctl_rate = rt->info[cix].dot11Rate; /* * Map without the basic rate bit. * * It's up to the caller to ensure that the basic * rate bit is stripped here. * * For HT, use the MCS rate bit. */ code &= IEEE80211_RATE_VAL; if (rt->info[i].phy == IEEE80211_T_HT) { code |= IEEE80211_RATE_MCS; } /* XXX assume the control rate is non-MCS? */ ctl_rate &= IEEE80211_RATE_VAL; rt->rateCodeToIndex[code] = i; /* * XXX for 11g the control rate to use for 5.5 and 11 Mb/s * depends on whether they are marked as basic rates; * the static tables are setup with an 11b-compatible * 2Mb/s rate which will work but is suboptimal * * NB: Control rate is always less than or equal to the * current rate, so control rate's reverse lookup entry * has been installed and following call is safe. */ rt->info[i].lpAckDuration = ieee80211_compute_duration(rt, WLAN_CTRL_FRAME_SIZE, ctl_rate, 0); rt->info[i].spAckDuration = ieee80211_compute_duration(rt, WLAN_CTRL_FRAME_SIZE, ctl_rate, IEEE80211_F_SHPREAMBLE); } #undef WLAN_CTRL_FRAME_SIZE } /* Setup all rate tables */ static void ieee80211_phy_init(void) { static struct ieee80211_rate_table * const ratetables[] = { &ieee80211_half_table, &ieee80211_quarter_table, &ieee80211_11na_table, &ieee80211_11ng_table, &ieee80211_turbog_table, &ieee80211_turboa_table, &ieee80211_11a_table, &ieee80211_11g_table, &ieee80211_11b_table }; int i; for (i = 0; i < nitems(ratetables); ++i) ieee80211_setup_ratetable(ratetables[i]); } SYSINIT(wlan_phy, SI_SUB_DRIVERS, SI_ORDER_FIRST, ieee80211_phy_init, NULL); const struct ieee80211_rate_table * ieee80211_get_ratetable(struct ieee80211_channel *c) { const struct ieee80211_rate_table *rt; /* XXX HT */ if (IEEE80211_IS_CHAN_HALF(c)) rt = &ieee80211_half_table; else if (IEEE80211_IS_CHAN_QUARTER(c)) rt = &ieee80211_quarter_table; else if (IEEE80211_IS_CHAN_HTA(c)) rt = &ieee80211_11na_table; else if (IEEE80211_IS_CHAN_HTG(c)) rt = &ieee80211_11ng_table; else if (IEEE80211_IS_CHAN_108G(c)) rt = &ieee80211_turbog_table; else if (IEEE80211_IS_CHAN_ST(c)) rt = &ieee80211_turboa_table; else if (IEEE80211_IS_CHAN_TURBO(c)) rt = &ieee80211_turboa_table; else if (IEEE80211_IS_CHAN_A(c)) rt = &ieee80211_11a_table; else if (IEEE80211_IS_CHAN_ANYG(c)) rt = &ieee80211_11g_table; else if (IEEE80211_IS_CHAN_B(c)) rt = &ieee80211_11b_table; else { /* NB: should not get here */ panic("%s: no rate table for channel; freq %u flags 0x%x\n", __func__, c->ic_freq, c->ic_flags); } return rt; } /* * Convert PLCP signal/rate field to 802.11 rate (.5Mbits/s) * * Note we do no parameter checking; this routine is mainly * used to derive an 802.11 rate for constructing radiotap * header data for rx frames. * * XXX might be a candidate for inline */ uint8_t ieee80211_plcp2rate(uint8_t plcp, enum ieee80211_phytype type) { if (type == IEEE80211_T_OFDM) { static const uint8_t ofdm_plcp2rate[16] = { [0xb] = 12, [0xf] = 18, [0xa] = 24, [0xe] = 36, [0x9] = 48, [0xd] = 72, [0x8] = 96, [0xc] = 108 }; return ofdm_plcp2rate[plcp & 0xf]; } if (type == IEEE80211_T_CCK) { static const uint8_t cck_plcp2rate[16] = { [0xa] = 2, /* 0x0a */ [0x4] = 4, /* 0x14 */ [0x7] = 11, /* 0x37 */ [0xe] = 22, /* 0x6e */ [0xc] = 44, /* 0xdc , actually PBCC */ }; return cck_plcp2rate[plcp & 0xf]; } return 0; } /* * Covert 802.11 rate to PLCP signal. */ uint8_t ieee80211_rate2plcp(int rate, enum ieee80211_phytype type) { /* XXX ignore type for now since rates are unique */ switch (rate) { /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ case 12: return 0xb; case 18: return 0xf; case 24: return 0xa; case 36: return 0xe; case 48: return 0x9; case 72: return 0xd; case 96: return 0x8; case 108: return 0xc; /* CCK rates (IEEE Std 802.11b-1999 page 15, subclause 18.2.3.3) */ case 2: return 10; case 4: return 20; case 11: return 55; case 22: return 110; /* IEEE Std 802.11g-2003 page 19, subclause 19.3.2.1 */ case 44: return 220; } return 0; /* XXX unsupported/unknown rate */ } #define CCK_SIFS_TIME 10 #define CCK_PREAMBLE_BITS 144 #define CCK_PLCP_BITS 48 #define OFDM_SIFS_TIME 16 #define OFDM_PREAMBLE_TIME 20 #define OFDM_PLCP_BITS 22 #define OFDM_SYMBOL_TIME 4 #define OFDM_HALF_SIFS_TIME 32 #define OFDM_HALF_PREAMBLE_TIME 40 #define OFDM_HALF_PLCP_BITS 22 #define OFDM_HALF_SYMBOL_TIME 8 #define OFDM_QUARTER_SIFS_TIME 64 #define OFDM_QUARTER_PREAMBLE_TIME 80 #define OFDM_QUARTER_PLCP_BITS 22 #define OFDM_QUARTER_SYMBOL_TIME 16 #define TURBO_SIFS_TIME 8 #define TURBO_PREAMBLE_TIME 14 #define TURBO_PLCP_BITS 22 #define TURBO_SYMBOL_TIME 4 /* * Compute the time to transmit a frame of length frameLen bytes * using the specified rate, phy, and short preamble setting. * SIFS is included. */ uint16_t ieee80211_compute_duration(const struct ieee80211_rate_table *rt, uint32_t frameLen, uint16_t rate, int isShortPreamble) { uint8_t rix = rt->rateCodeToIndex[rate]; uint32_t bitsPerSymbol, numBits, numSymbols, phyTime, txTime; uint32_t kbps; KASSERT(rix != (uint8_t)-1, ("rate %d has no info", rate)); kbps = rt->info[rix].rateKbps; if (kbps == 0) /* XXX bandaid for channel changes */ return 0; switch (rt->info[rix].phy) { case IEEE80211_T_CCK: phyTime = CCK_PREAMBLE_BITS + CCK_PLCP_BITS; if (isShortPreamble && rt->info[rix].shortPreamble) phyTime >>= 1; numBits = frameLen << 3; txTime = CCK_SIFS_TIME + phyTime + ((numBits * 1000)/kbps); break; case IEEE80211_T_OFDM: bitsPerSymbol = (kbps * OFDM_SYMBOL_TIME) / 1000; KASSERT(bitsPerSymbol != 0, ("full rate bps")); numBits = OFDM_PLCP_BITS + (frameLen << 3); numSymbols = howmany(numBits, bitsPerSymbol); txTime = OFDM_SIFS_TIME + OFDM_PREAMBLE_TIME + (numSymbols * OFDM_SYMBOL_TIME); break; case IEEE80211_T_OFDM_HALF: bitsPerSymbol = (kbps * OFDM_HALF_SYMBOL_TIME) / 1000; KASSERT(bitsPerSymbol != 0, ("1/4 rate bps")); numBits = OFDM_PLCP_BITS + (frameLen << 3); numSymbols = howmany(numBits, bitsPerSymbol); txTime = OFDM_HALF_SIFS_TIME + OFDM_HALF_PREAMBLE_TIME + (numSymbols * OFDM_HALF_SYMBOL_TIME); break; case IEEE80211_T_OFDM_QUARTER: bitsPerSymbol = (kbps * OFDM_QUARTER_SYMBOL_TIME) / 1000; KASSERT(bitsPerSymbol != 0, ("1/2 rate bps")); numBits = OFDM_PLCP_BITS + (frameLen << 3); numSymbols = howmany(numBits, bitsPerSymbol); txTime = OFDM_QUARTER_SIFS_TIME + OFDM_QUARTER_PREAMBLE_TIME + (numSymbols * OFDM_QUARTER_SYMBOL_TIME); break; case IEEE80211_T_TURBO: /* we still save OFDM rates in kbps - so double them */ bitsPerSymbol = ((kbps << 1) * TURBO_SYMBOL_TIME) / 1000; KASSERT(bitsPerSymbol != 0, ("turbo bps")); numBits = TURBO_PLCP_BITS + (frameLen << 3); numSymbols = howmany(numBits, bitsPerSymbol); txTime = TURBO_SIFS_TIME + TURBO_PREAMBLE_TIME + (numSymbols * TURBO_SYMBOL_TIME); break; default: panic("%s: unknown phy %u (rate %u)\n", __func__, rt->info[rix].phy, rate); break; } return txTime; } static const uint16_t ht20_bps[32] = { 26, 52, 78, 104, 156, 208, 234, 260, 52, 104, 156, 208, 312, 416, 468, 520, 78, 156, 234, 312, 468, 624, 702, 780, 104, 208, 312, 416, 624, 832, 936, 1040 }; static const uint16_t ht40_bps[32] = { 54, 108, 162, 216, 324, 432, 486, 540, 108, 216, 324, 432, 648, 864, 972, 1080, 162, 324, 486, 648, 972, 1296, 1458, 1620, 216, 432, 648, 864, 1296, 1728, 1944, 2160 }; #define OFDM_PLCP_BITS 22 #define HT_L_STF 8 #define HT_L_LTF 8 #define HT_L_SIG 4 #define HT_SIG 8 #define HT_STF 4 #define HT_LTF(n) ((n) * 4) -#define HT_RC_2_MCS(_rc) ((_rc) & 0xf) +#define HT_RC_2_MCS(_rc) ((_rc) & 0x1f) #define HT_RC_2_STREAMS(_rc) ((((_rc) & 0x78) >> 3) + 1) #define IS_HT_RATE(_rc) ( (_rc) & IEEE80211_RATE_MCS) /* * Calculate the transmit duration of an 11n frame. */ uint32_t ieee80211_compute_duration_ht(uint32_t frameLen, uint16_t rate, int streams, int isht40, int isShortGI) { uint32_t bitsPerSymbol, numBits, numSymbols, txTime; KASSERT(rate & IEEE80211_RATE_MCS, ("not mcs %d", rate)); KASSERT((rate &~ IEEE80211_RATE_MCS) < 31, ("bad mcs 0x%x", rate)); if (isht40) bitsPerSymbol = ht40_bps[rate & 0x1f]; else bitsPerSymbol = ht20_bps[rate & 0x1f]; numBits = OFDM_PLCP_BITS + (frameLen << 3); numSymbols = howmany(numBits, bitsPerSymbol); if (isShortGI) txTime = ((numSymbols * 18) + 4) / 5; /* 3.6us */ else txTime = numSymbols * 4; /* 4us */ return txTime + HT_L_STF + HT_L_LTF + HT_L_SIG + HT_SIG + HT_STF + HT_LTF(streams); } #undef IS_HT_RATE #undef HT_RC_2_STREAMS #undef HT_RC_2_MCS #undef HT_LTF #undef HT_STF #undef HT_SIG #undef HT_L_SIG #undef HT_L_LTF #undef HT_L_STF #undef OFDM_PLCP_BITS Index: projects/release-pkg/sys/net80211/ieee80211_phy.h =================================================================== --- projects/release-pkg/sys/net80211/ieee80211_phy.h (revision 297604) +++ projects/release-pkg/sys/net80211/ieee80211_phy.h (revision 297605) @@ -1,202 +1,210 @@ /*- * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting * 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 _NET80211_IEEE80211_PHY_H_ #define _NET80211_IEEE80211_PHY_H_ #ifdef _KERNEL /* * IEEE 802.11 PHY-related definitions. */ /* * Contention window (slots). */ #define IEEE80211_CW_MAX 1023 /* aCWmax */ #define IEEE80211_CW_MIN_0 31 /* DS/CCK aCWmin, ERP aCWmin(0) */ #define IEEE80211_CW_MIN_1 15 /* OFDM aCWmin, ERP aCWmin(1) */ /* * SIFS (microseconds). */ #define IEEE80211_DUR_SIFS 10 /* DS/CCK/ERP SIFS */ #define IEEE80211_DUR_OFDM_SIFS 16 /* OFDM SIFS */ /* * Slot time (microseconds). */ #define IEEE80211_DUR_SLOT 20 /* DS/CCK slottime, ERP long slottime */ #define IEEE80211_DUR_SHSLOT 9 /* ERP short slottime */ #define IEEE80211_DUR_OFDM_SLOT 9 /* OFDM slottime */ #define IEEE80211_GET_SLOTTIME(ic) \ ((ic->ic_flags & IEEE80211_F_SHSLOT) ? \ IEEE80211_DUR_SHSLOT : IEEE80211_DUR_SLOT) /* * DIFS (microseconds). */ #define IEEE80211_DUR_DIFS(sifs, slot) ((sifs) + 2 * (slot)) struct ieee80211_channel; #define IEEE80211_RATE_TABLE_SIZE 128 struct ieee80211_rate_table { int rateCount; /* NB: for proper padding */ uint8_t rateCodeToIndex[256]; /* back mapping */ struct { uint8_t phy; /* CCK/OFDM/TURBO */ uint32_t rateKbps; /* transfer rate in kbs */ uint8_t shortPreamble; /* mask for enabling short * preamble in CCK rate code */ uint8_t dot11Rate; /* value for supported rates * info element of MLME */ uint8_t ctlRateIndex; /* index of next lower basic * rate; used for dur. calcs */ uint16_t lpAckDuration; /* long preamble ACK dur. */ uint16_t spAckDuration; /* short preamble ACK dur. */ } info[IEEE80211_RATE_TABLE_SIZE]; }; const struct ieee80211_rate_table *ieee80211_get_ratetable( struct ieee80211_channel *); static __inline__ uint8_t ieee80211_ack_rate(const struct ieee80211_rate_table *rt, uint8_t rate) { /* * XXX Assert this is for a legacy rate; not for an MCS rate. * If the caller wishes to use it for a basic rate, they should * clear the high bit first. */ KASSERT(! (rate & 0x80), ("rate %d is basic/mcs?", rate)); uint8_t cix = rt->info[rt->rateCodeToIndex[rate & IEEE80211_RATE_VAL]].ctlRateIndex; KASSERT(cix != (uint8_t)-1, ("rate %d has no info", rate)); return rt->info[cix].dot11Rate; } static __inline__ uint8_t ieee80211_ctl_rate(const struct ieee80211_rate_table *rt, uint8_t rate) { /* * XXX Assert this is for a legacy rate; not for an MCS rate. * If the caller wishes to use it for a basic rate, they should * clear the high bit first. */ KASSERT(! (rate & 0x80), ("rate %d is basic/mcs?", rate)); uint8_t cix = rt->info[rt->rateCodeToIndex[rate & IEEE80211_RATE_VAL]].ctlRateIndex; KASSERT(cix != (uint8_t)-1, ("rate %d has no info", rate)); return rt->info[cix].dot11Rate; } static __inline__ enum ieee80211_phytype ieee80211_rate2phytype(const struct ieee80211_rate_table *rt, uint8_t rate) { /* * XXX Assert this is for a legacy rate; not for an MCS rate. * If the caller wishes to use it for a basic rate, they should * clear the high bit first. */ KASSERT(! (rate & 0x80), ("rate %d is basic/mcs?", rate)); uint8_t rix = rt->rateCodeToIndex[rate & IEEE80211_RATE_VAL]; KASSERT(rix != (uint8_t)-1, ("rate %d has no info", rate)); return rt->info[rix].phy; } static __inline__ int ieee80211_isratevalid(const struct ieee80211_rate_table *rt, uint8_t rate) { /* * XXX Assert this is for a legacy rate; not for an MCS rate. * If the caller wishes to use it for a basic rate, they should * clear the high bit first. */ KASSERT(! (rate & 0x80), ("rate %d is basic/mcs?", rate)); return rt->rateCodeToIndex[rate] != (uint8_t)-1; } /* * Calculate ACK field for * o non-fragment data frames * o management frames * sent using rate, phy and short preamble setting. */ static __inline__ uint16_t ieee80211_ack_duration(const struct ieee80211_rate_table *rt, uint8_t rate, int isShortPreamble) { uint8_t rix = rt->rateCodeToIndex[rate]; KASSERT(rix != (uint8_t)-1, ("rate %d has no info", rate)); if (isShortPreamble) { KASSERT(rt->info[rix].spAckDuration != 0, ("shpreamble ack dur is not computed!\n")); return rt->info[rix].spAckDuration; } else { KASSERT(rt->info[rix].lpAckDuration != 0, ("lgpreamble ack dur is not computed!\n")); return rt->info[rix].lpAckDuration; } } static __inline__ uint8_t ieee80211_legacy_rate_lookup(const struct ieee80211_rate_table *rt, uint8_t rate) { return (rt->rateCodeToIndex[rate & IEEE80211_RATE_VAL]); } /* * Compute the time to transmit a frame of length frameLen bytes * using the specified 802.11 rate code, phy, and short preamble * setting. * * NB: SIFS is included. */ uint16_t ieee80211_compute_duration(const struct ieee80211_rate_table *, uint32_t frameLen, uint16_t rate, int isShortPreamble); /* * Convert PLCP signal/rate field to 802.11 rate code (.5Mbits/s) */ uint8_t ieee80211_plcp2rate(uint8_t, enum ieee80211_phytype); /* * Convert 802.11 rate code to PLCP signal. */ uint8_t ieee80211_rate2plcp(int, enum ieee80211_phytype); +/* + * 802.11n rate manipulation. + */ + +#define IEEE80211_HT_RC_2_MCS(_rc) ((_rc) & 0x1f) +#define IEEE80211_HT_RC_2_STREAMS(_rc) ((((_rc) & 0x78) >> 3) + 1) +#define IEEE80211_IS_HT_RATE(_rc) ( (_rc) & IEEE80211_RATE_MCS) + uint32_t ieee80211_compute_duration_ht(uint32_t frameLen, uint16_t rate, int streams, int isht40, int isShortGI); #endif /* _KERNEL */ #endif /* !_NET80211_IEEE80211_PHY_H_ */ Index: projects/release-pkg/sys/net80211/ieee80211_sta.c =================================================================== --- projects/release-pkg/sys/net80211/ieee80211_sta.c (revision 297604) +++ projects/release-pkg/sys/net80211/ieee80211_sta.c (revision 297605) @@ -1,1871 +1,1867 @@ /*- * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting * 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 #ifdef __FreeBSD__ __FBSDID("$FreeBSD$"); #endif /* * IEEE 802.11 Station mode support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #include #include #define IEEE80211_RATE2MBS(r) (((r) & IEEE80211_RATE_VAL) / 2) static void sta_vattach(struct ieee80211vap *); static void sta_beacon_miss(struct ieee80211vap *); static int sta_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int sta_input(struct ieee80211_node *, struct mbuf *, const struct ieee80211_rx_stats *, int, int); static void sta_recv_mgmt(struct ieee80211_node *, struct mbuf *, int subtype, const struct ieee80211_rx_stats *, int rssi, int nf); static void sta_recv_ctl(struct ieee80211_node *, struct mbuf *, int subtype); void ieee80211_sta_attach(struct ieee80211com *ic) { ic->ic_vattach[IEEE80211_M_STA] = sta_vattach; } void ieee80211_sta_detach(struct ieee80211com *ic) { } static void sta_vdetach(struct ieee80211vap *vap) { } static void sta_vattach(struct ieee80211vap *vap) { vap->iv_newstate = sta_newstate; vap->iv_input = sta_input; vap->iv_recv_mgmt = sta_recv_mgmt; vap->iv_recv_ctl = sta_recv_ctl; vap->iv_opdetach = sta_vdetach; vap->iv_bmiss = sta_beacon_miss; } /* * Handle a beacon miss event. The common code filters out * spurious events that can happen when scanning and/or before * reaching RUN state. */ static void sta_beacon_miss(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); KASSERT((ic->ic_flags & IEEE80211_F_SCAN) == 0, ("scanning")); KASSERT(vap->iv_state >= IEEE80211_S_RUN, ("wrong state %s", ieee80211_state_name[vap->iv_state])); IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "beacon miss, mode %s state %s\n", ieee80211_opmode_name[vap->iv_opmode], ieee80211_state_name[vap->iv_state]); if (vap->iv_state == IEEE80211_S_CSA) { /* * A Channel Switch is pending; assume we missed the * beacon that would've completed the process and just * force the switch. If we made a mistake we'll not * find the AP on the new channel and fall back to a * normal scan. */ ieee80211_csa_completeswitch(ic); return; } if (++vap->iv_bmiss_count < vap->iv_bmiss_max) { /* * Send a directed probe req before falling back to a * scan; if we receive a response ic_bmiss_count will * be reset. Some cards mistakenly report beacon miss * so this avoids the expensive scan if the ap is * still there. */ ieee80211_send_probereq(vap->iv_bss, vap->iv_myaddr, vap->iv_bss->ni_bssid, vap->iv_bss->ni_bssid, vap->iv_bss->ni_essid, vap->iv_bss->ni_esslen); return; } callout_stop(&vap->iv_swbmiss); vap->iv_bmiss_count = 0; vap->iv_stats.is_beacon_miss++; if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { #ifdef IEEE80211_SUPPORT_SUPERG struct ieee80211com *ic = vap->iv_ic; /* * If we receive a beacon miss interrupt when using * dynamic turbo, attempt to switch modes before * reassociating. */ if (IEEE80211_ATH_CAP(vap, vap->iv_bss, IEEE80211_NODE_TURBOP)) ieee80211_dturbo_switch(vap, ic->ic_bsschan->ic_flags ^ IEEE80211_CHAN_TURBO); #endif /* * Try to reassociate before scanning for a new ap. */ ieee80211_new_state(vap, IEEE80211_S_ASSOC, 1); } else { /* * Somebody else is controlling state changes (e.g. * a user-mode app) don't do anything that would * confuse them; just drop into scan mode so they'll * notified of the state change and given control. */ ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); } } /* * Handle deauth with reason. We retry only for * the cases where we might succeed. Otherwise * we downgrade the ap and scan. */ static void sta_authretry(struct ieee80211vap *vap, struct ieee80211_node *ni, int reason) { switch (reason) { case IEEE80211_STATUS_SUCCESS: /* NB: MLME assoc */ case IEEE80211_STATUS_TIMEOUT: case IEEE80211_REASON_ASSOC_EXPIRE: case IEEE80211_REASON_NOT_AUTHED: case IEEE80211_REASON_NOT_ASSOCED: case IEEE80211_REASON_ASSOC_LEAVE: case IEEE80211_REASON_ASSOC_NOT_AUTHED: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); break; default: ieee80211_scan_assoc_fail(vap, vap->iv_bss->ni_macaddr, reason); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; } } static void sta_swbmiss_start(struct ieee80211vap *vap) { if (vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) { /* * Start s/w beacon miss timer for devices w/o * hardware support. We fudge a bit here since * we're doing this in software. */ vap->iv_swbmiss_period = IEEE80211_TU_TO_TICKS( 2 * vap->iv_bmissthreshold * vap->iv_bss->ni_intval); vap->iv_swbmiss_count = 0; callout_reset(&vap->iv_swbmiss, vap->iv_swbmiss_period, ieee80211_swbmiss, vap); } } /* * IEEE80211_M_STA vap state machine handler. * This routine handles the main states in the 802.11 protocol. */ static int sta_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; enum ieee80211_state ostate; IEEE80211_LOCK_ASSERT(ic); ostate = vap->iv_state; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (%d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg); vap->iv_state = nstate; /* state transition */ callout_stop(&vap->iv_mgtsend); /* XXX callout_drain */ if (ostate != IEEE80211_S_SCAN) ieee80211_cancel_scan(vap); /* background scan */ ni = vap->iv_bss; /* NB: no reference held */ if (vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) callout_stop(&vap->iv_swbmiss); switch (nstate) { case IEEE80211_S_INIT: switch (ostate) { case IEEE80211_S_SLEEP: /* XXX wakeup */ /* XXX driver hook to wakeup the hardware? */ case IEEE80211_S_RUN: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DISASSOC, IEEE80211_REASON_ASSOC_LEAVE); ieee80211_sta_leave(ni); break; case IEEE80211_S_ASSOC: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_AUTH_LEAVE); break; case IEEE80211_S_SCAN: ieee80211_cancel_scan(vap); break; default: break; } if (ostate != IEEE80211_S_INIT) { /* NB: optimize INIT -> INIT case */ ieee80211_reset_bss(vap); } if (vap->iv_auth->ia_detach != NULL) vap->iv_auth->ia_detach(vap); break; case IEEE80211_S_SCAN: switch (ostate) { case IEEE80211_S_INIT: /* * Initiate a scan. We can come here as a result * of an IEEE80211_IOC_SCAN_REQ too in which case * the vap will be marked with IEEE80211_FEXT_SCANREQ * and the scan request parameters will be present * in iv_scanreq. Otherwise we do the default. */ if (vap->iv_flags_ext & IEEE80211_FEXT_SCANREQ) { ieee80211_check_scan(vap, vap->iv_scanreq_flags, vap->iv_scanreq_duration, vap->iv_scanreq_mindwell, vap->iv_scanreq_maxdwell, vap->iv_scanreq_nssid, vap->iv_scanreq_ssid); vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANREQ; } else ieee80211_check_scan_current(vap); break; case IEEE80211_S_SCAN: case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: /* * These can happen either because of a timeout * on an assoc/auth response or because of a * change in state that requires a reset. For * the former we're called with a non-zero arg * that is the cause for the failure; pass this * to the scan code so it can update state. * Otherwise trigger a new scan unless we're in * manual roaming mode in which case an application * must issue an explicit scan request. */ if (arg != 0) ieee80211_scan_assoc_fail(vap, vap->iv_bss->ni_macaddr, arg); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; case IEEE80211_S_SLEEP: /* beacon miss */ /* * XXX if in sleep we need to wakeup the hardware. */ /* FALLTHROUGH */ case IEEE80211_S_RUN: /* beacon miss */ /* * Beacon miss. Notify user space and if not * under control of a user application (roaming * manual) kick off a scan to re-connect. */ ieee80211_sta_leave(ni); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; default: goto invalid; } break; case IEEE80211_S_AUTH: switch (ostate) { case IEEE80211_S_INIT: case IEEE80211_S_SCAN: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); break; case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: switch (arg & 0xff) { case IEEE80211_FC0_SUBTYPE_AUTH: /* ??? */ IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 2); break; case IEEE80211_FC0_SUBTYPE_DEAUTH: sta_authretry(vap, ni, arg>>8); break; } break; case IEEE80211_S_SLEEP: case IEEE80211_S_RUN: switch (arg & 0xff) { case IEEE80211_FC0_SUBTYPE_AUTH: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 2); vap->iv_state = IEEE80211_S_RUN; /* stay RUN */ break; case IEEE80211_FC0_SUBTYPE_DEAUTH: ieee80211_sta_leave(ni); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { /* try to reauth */ IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); } break; } break; default: goto invalid; } break; case IEEE80211_S_ASSOC: switch (ostate) { case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_ASSOC_REQ, 0); break; case IEEE80211_S_SLEEP: /* cannot happen */ case IEEE80211_S_RUN: ieee80211_sta_leave(ni); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { IEEE80211_SEND_MGMT(ni, arg ? IEEE80211_FC0_SUBTYPE_REASSOC_REQ : IEEE80211_FC0_SUBTYPE_ASSOC_REQ, 0); } break; default: goto invalid; } break; case IEEE80211_S_RUN: if (vap->iv_flags & IEEE80211_F_WPA) { /* XXX validate prerequisites */ } switch (ostate) { case IEEE80211_S_RUN: case IEEE80211_S_CSA: break; case IEEE80211_S_AUTH: /* when join is done in fw */ case IEEE80211_S_ASSOC: #ifdef IEEE80211_DEBUG if (ieee80211_msg_debug(vap)) { ieee80211_note(vap, "%s with %s ssid ", (vap->iv_opmode == IEEE80211_M_STA ? "associated" : "synchronized"), ether_sprintf(ni->ni_bssid)); ieee80211_print_essid(vap->iv_bss->ni_essid, ni->ni_esslen); /* XXX MCS/HT */ printf(" channel %d start %uMb\n", ieee80211_chan2ieee(ic, ic->ic_curchan), IEEE80211_RATE2MBS(ni->ni_txrate)); } #endif ieee80211_scan_assoc_success(vap, ni->ni_macaddr); ieee80211_notify_node_join(ni, arg == IEEE80211_FC0_SUBTYPE_ASSOC_RESP); break; case IEEE80211_S_SLEEP: /* Wake up from sleep */ vap->iv_sta_ps(vap, 0); break; default: goto invalid; } ieee80211_sync_curchan(ic); if (ostate != IEEE80211_S_RUN) sta_swbmiss_start(vap); /* * When 802.1x is not in use mark the port authorized * at this point so traffic can flow. */ if (ni->ni_authmode != IEEE80211_AUTH_8021X) ieee80211_node_authorize(ni); /* * Fake association when joining an existing bss. * * Don't do this if we're doing SLEEP->RUN. */ if (ic->ic_newassoc != NULL && ostate != IEEE80211_S_SLEEP) ic->ic_newassoc(vap->iv_bss, (ostate != IEEE80211_S_RUN)); break; case IEEE80211_S_CSA: if (ostate != IEEE80211_S_RUN) goto invalid; break; case IEEE80211_S_SLEEP: sta_swbmiss_start(vap); vap->iv_sta_ps(vap, 1); break; default: invalid: IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: unexpected state transition %s -> %s\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); break; } return 0; } /* * Return non-zero if the frame is an echo of a multicast * frame sent by ourself. The dir is known to be DSTODS. */ static __inline int isdstods_mcastecho(struct ieee80211vap *vap, const struct ieee80211_frame *wh) { #define QWH4(wh) ((const struct ieee80211_qosframe_addr4 *)wh) #define WH4(wh) ((const struct ieee80211_frame_addr4 *)wh) const uint8_t *sa; KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode")); if (!IEEE80211_IS_MULTICAST(wh->i_addr3)) return 0; sa = IEEE80211_QOS_HAS_SEQ(wh) ? QWH4(wh)->i_addr4 : WH4(wh)->i_addr4; return IEEE80211_ADDR_EQ(sa, vap->iv_myaddr); #undef WH4 #undef QWH4 } /* * Return non-zero if the frame is an echo of a multicast * frame sent by ourself. The dir is known to be FROMDS. */ static __inline int isfromds_mcastecho(struct ieee80211vap *vap, const struct ieee80211_frame *wh) { KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode")); if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) return 0; return IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_myaddr); } /* * Decide if a received management frame should be * printed when debugging is enabled. This filters some * of the less interesting frames that come frequently * (e.g. beacons). */ static __inline int doprint(struct ieee80211vap *vap, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_BEACON: return (vap->iv_ic->ic_flags & IEEE80211_F_SCAN); case IEEE80211_FC0_SUBTYPE_PROBE_REQ: return 0; } return 1; } /* * Process a received frame. The node associated with the sender * should be supplied. If nothing was found in the node table then * the caller is assumed to supply a reference to iv_bss instead. * The RSSI and a timestamp are also supplied. The RSSI data is used * during AP scanning to select a AP to associate with; it can have * any units so long as values have consistent units and higher values * mean ``better signal''. The receive timestamp is currently not used * by the 802.11 layer. */ static int sta_input(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = vap->iv_ifp; struct ieee80211_frame *wh; struct ieee80211_key *key; struct ether_header *eh; int hdrspace, need_tap = 1; /* mbuf need to be tapped. */ uint8_t dir, type, subtype, qos; uint8_t *bssid; if (m->m_flags & M_AMPDU_MPDU) { /* * Fastpath for A-MPDU reorder q resubmission. Frames * w/ M_AMPDU_MPDU marked have already passed through * here but were received out of order and been held on * the reorder queue. When resubmitted they are marked * with the M_AMPDU_MPDU flag and we can bypass most of * the normal processing. */ wh = mtod(m, struct ieee80211_frame *); type = IEEE80211_FC0_TYPE_DATA; dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; subtype = IEEE80211_FC0_SUBTYPE_QOS; hdrspace = ieee80211_hdrspace(ic, wh); /* XXX optimize? */ goto resubmit_ampdu; } KASSERT(ni != NULL, ("null node")); ni->ni_inact = ni->ni_inact_reload; type = -1; /* undefined */ if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_min)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "too short (1): len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } /* * Bit of a cheat here, we use a pointer for a 3-address * frame format but don't reference fields past outside * ieee80211_frame_min w/o first validating the data is * present. */ wh = mtod(m, struct ieee80211_frame *); if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) != IEEE80211_FC0_VERSION_0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "wrong version, fc %02x:%02x", wh->i_fc[0], wh->i_fc[1]); vap->iv_stats.is_rx_badversion++; goto err; } dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { bssid = wh->i_addr2; if (!IEEE80211_ADDR_EQ(bssid, ni->ni_bssid)) { /* not interested in */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, bssid, NULL, "%s", "not to bss"); vap->iv_stats.is_rx_wrongbss++; goto out; } /* * Some devices may be in a promiscuous mode * where they receive frames for multiple station * addresses. * * If we receive a data frame that isn't * destined to our VAP MAC, drop it. * * XXX TODO: This is only enforced when not scanning; * XXX it assumes a software-driven scan will put the NIC * XXX into a "no data frames" mode before setting this * XXX flag. Otherwise it may be possible that we'll still * XXX process data frames whilst scanning. */ if ((! IEEE80211_IS_MULTICAST(wh->i_addr1)) && (! IEEE80211_ADDR_EQ(wh->i_addr1, IF_LLADDR(ifp)))) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, bssid, NULL, "not to cur sta: lladdr=%6D, addr1=%6D", IF_LLADDR(ifp), ":", wh->i_addr1, ":"); vap->iv_stats.is_rx_wrongbss++; goto out; } IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; if ( IEEE80211_HAS_SEQ(type, subtype) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { uint8_t tid = ieee80211_gettid(wh); if (IEEE80211_QOS_HAS_SEQ(wh) && TID_TO_WME_AC(tid) >= WME_AC_VI) ic->ic_wme.wme_hipri_traffic++; if (! ieee80211_check_rxseq(ni, wh, bssid)) goto out; } } switch (type) { case IEEE80211_FC0_TYPE_DATA: hdrspace = ieee80211_hdrspace(ic, wh); if (m->m_len < hdrspace && (m = m_pullup(m, hdrspace)) == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "data too short: expecting %u", hdrspace); vap->iv_stats.is_rx_tooshort++; goto out; /* XXX */ } /* * Handle A-MPDU re-ordering. If the frame is to be * processed directly then ieee80211_ampdu_reorder * will return 0; otherwise it has consumed the mbuf * and we should do nothing more with it. */ if ((m->m_flags & M_AMPDU) && (dir == IEEE80211_FC1_DIR_FROMDS || dir == IEEE80211_FC1_DIR_DSTODS) && ieee80211_ampdu_reorder(ni, m) != 0) { m = NULL; goto out; } resubmit_ampdu: if (dir == IEEE80211_FC1_DIR_FROMDS) { if ((ifp->if_flags & IFF_SIMPLEX) && isfromds_mcastecho(vap, wh)) { /* * In IEEE802.11 network, multicast * packets sent from "me" are broadcast * from the AP; silently discard for * SIMPLEX interface. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "%s", "multicast echo"); vap->iv_stats.is_rx_mcastecho++; goto out; } if ((vap->iv_flags & IEEE80211_F_DWDS) && IEEE80211_IS_MULTICAST(wh->i_addr1)) { /* * DWDS sta's must drop 3-address mcast frames * as they will be sent separately as a 4-addr * frame. Accepting the 3-addr frame will * confuse the bridge into thinking the sending * sta is located at the end of WDS link. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "3-address data", "%s", "DWDS enabled"); vap->iv_stats.is_rx_mcastecho++; goto out; } } else if (dir == IEEE80211_FC1_DIR_DSTODS) { if ((vap->iv_flags & IEEE80211_F_DWDS) == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "4-address data", "%s", "DWDS not enabled"); vap->iv_stats.is_rx_wrongdir++; goto out; } if ((ifp->if_flags & IFF_SIMPLEX) && isdstods_mcastecho(vap, wh)) { /* * In IEEE802.11 network, multicast * packets sent from "me" are broadcast * from the AP; silently discard for * SIMPLEX interface. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "4-address data", "%s", "multicast echo"); vap->iv_stats.is_rx_mcastecho++; goto out; } } else { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "incorrect dir 0x%x", dir); vap->iv_stats.is_rx_wrongdir++; goto out; } /* * Handle privacy requirements. Note that we * must not be preempted from here until after * we (potentially) call ieee80211_crypto_demic; * otherwise we may violate assumptions in the * crypto cipher modules used to do delayed update * of replay sequence numbers. */ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "WEP", "%s", "PRIVACY off"); vap->iv_stats.is_rx_noprivacy++; IEEE80211_NODE_STAT(ni, rx_noprivacy); goto out; } key = ieee80211_crypto_decap(ni, m, hdrspace); if (key == NULL) { /* NB: stats+msgs handled in crypto_decap */ IEEE80211_NODE_STAT(ni, rx_wepfail); goto out; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; } else { /* XXX M_WEP and IEEE80211_F_PRIVACY */ key = NULL; } /* * Save QoS bits for use below--before we strip the header. */ if (subtype == IEEE80211_FC0_SUBTYPE_QOS) { qos = (dir == IEEE80211_FC1_DIR_DSTODS) ? ((struct ieee80211_qosframe_addr4 *)wh)->i_qos[0] : ((struct ieee80211_qosframe *)wh)->i_qos[0]; } else qos = 0; /* * Next up, any fragmentation. */ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { m = ieee80211_defrag(ni, m, hdrspace); if (m == NULL) { /* Fragment dropped or frame not complete yet */ goto out; } } wh = NULL; /* no longer valid, catch any uses */ /* * Next strip any MSDU crypto bits. */ if (key != NULL && !ieee80211_crypto_demic(vap, key, m, 0)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "%s", "demic error"); vap->iv_stats.is_rx_demicfail++; IEEE80211_NODE_STAT(ni, rx_demicfail); goto out; } /* copy to listener after decrypt */ if (ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); need_tap = 0; /* * Finally, strip the 802.11 header. */ m = ieee80211_decap(vap, m, hdrspace); if (m == NULL) { /* XXX mask bit to check for both */ /* don't count Null data frames as errors */ if (subtype == IEEE80211_FC0_SUBTYPE_NODATA || subtype == IEEE80211_FC0_SUBTYPE_QOS_NULL) goto out; IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "%s", "decap error"); vap->iv_stats.is_rx_decap++; IEEE80211_NODE_STAT(ni, rx_decap); goto err; } eh = mtod(m, struct ether_header *); if (!ieee80211_node_is_authorized(ni)) { /* * Deny any non-PAE frames received prior to * authorization. For open/shared-key * authentication the port is mark authorized * after authentication completes. For 802.1x * the port is not marked authorized by the * authenticator until the handshake has completed. */ if (eh->ether_type != htons(ETHERTYPE_PAE)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, eh->ether_shost, "data", "unauthorized port: ether type 0x%x len %u", eh->ether_type, m->m_pkthdr.len); vap->iv_stats.is_rx_unauth++; IEEE80211_NODE_STAT(ni, rx_unauth); goto err; } } else { /* * When denying unencrypted frames, discard * any non-PAE frames received without encryption. */ if ((vap->iv_flags & IEEE80211_F_DROPUNENC) && (key == NULL && (m->m_flags & M_WEP) == 0) && eh->ether_type != htons(ETHERTYPE_PAE)) { /* * Drop unencrypted frames. */ vap->iv_stats.is_rx_unencrypted++; IEEE80211_NODE_STAT(ni, rx_unencrypted); goto out; } } /* XXX require HT? */ if (qos & IEEE80211_QOS_AMSDU) { m = ieee80211_decap_amsdu(ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; } else { #ifdef IEEE80211_SUPPORT_SUPERG m = ieee80211_decap_fastframe(vap, ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; #endif } ieee80211_deliver_data(vap, ni, m); return IEEE80211_FC0_TYPE_DATA; case IEEE80211_FC0_TYPE_MGT: vap->iv_stats.is_rx_mgmt++; IEEE80211_NODE_STAT(ni, rx_mgmt); if (dir != IEEE80211_FC1_DIR_NODS) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "incorrect dir 0x%x", dir); vap->iv_stats.is_rx_wrongdir++; goto err; } if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "mgt", "too short: len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } #ifdef IEEE80211_DEBUG if ((ieee80211_msg_debug(vap) && doprint(vap, subtype)) || ieee80211_msg_dumppkts(vap)) { if_printf(ifp, "received %s from %s rssi %d\n", ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], ether_sprintf(wh->i_addr2), rssi); } #endif if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { if (subtype != IEEE80211_FC0_SUBTYPE_AUTH) { /* * Only shared key auth frames with a challenge * should be encrypted, discard all others. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], "%s", "WEP set but not permitted"); vap->iv_stats.is_rx_mgtdiscard++; /* XXX */ goto out; } if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "mgt", "%s", "WEP set but PRIVACY off"); vap->iv_stats.is_rx_noprivacy++; goto out; } hdrspace = ieee80211_hdrspace(ic, wh); key = ieee80211_crypto_decap(ni, m, hdrspace); if (key == NULL) { /* NB: stats+msgs handled in crypto_decap */ goto out; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; } vap->iv_recv_mgmt(ni, m, subtype, rxs, rssi, nf); goto out; case IEEE80211_FC0_TYPE_CTL: vap->iv_stats.is_rx_ctl++; IEEE80211_NODE_STAT(ni, rx_ctrl); vap->iv_recv_ctl(ni, m, subtype); goto out; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "bad frame type 0x%x", type); /* should not come here */ break; } err: if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); out: if (m != NULL) { if (need_tap && ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); m_freem(m); } return type; } static void sta_auth_open(struct ieee80211_node *ni, struct ieee80211_frame *wh, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; if (ni->ni_authmode == IEEE80211_AUTH_SHARED) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "open auth", "bad sta auth mode %u", ni->ni_authmode); vap->iv_stats.is_rx_bad_auth++; /* XXX */ return; } if (vap->iv_state != IEEE80211_S_AUTH || seq != IEEE80211_AUTH_OPEN_RESPONSE) { vap->iv_stats.is_rx_bad_auth++; return; } if (status != 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, "open auth failed (reason %d)", status); vap->iv_stats.is_rx_auth_fail++; vap->iv_stats.is_rx_authfail_code = status; ieee80211_new_state(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_STATUS); } else ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); } static void sta_auth_shared(struct ieee80211_node *ni, struct ieee80211_frame *wh, uint8_t *frm, uint8_t *efrm, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; uint8_t *challenge; int estatus; /* * NB: this can happen as we allow pre-shared key * authentication to be enabled w/o wep being turned * on so that configuration of these can be done * in any order. It may be better to enforce the * ordering in which case this check would just be * for sanity/consistency. */ if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "%s", " PRIVACY is disabled"); estatus = IEEE80211_STATUS_ALG; goto bad; } /* * Pre-shared key authentication is evil; accept * it only if explicitly configured (it is supported * mainly for compatibility with clients like OS X). */ if (ni->ni_authmode != IEEE80211_AUTH_AUTO && ni->ni_authmode != IEEE80211_AUTH_SHARED) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad sta auth mode %u", ni->ni_authmode); vap->iv_stats.is_rx_bad_auth++; /* XXX maybe a unique error? */ estatus = IEEE80211_STATUS_ALG; goto bad; } challenge = NULL; if (frm + 1 < efrm) { if ((frm[1] + 2) > (efrm - frm)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "ie %d/%d too long", frm[0], (frm[1] + 2) - (efrm - frm)); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (*frm == IEEE80211_ELEMID_CHALLENGE) challenge = frm; frm += frm[1] + 2; } switch (seq) { case IEEE80211_AUTH_SHARED_CHALLENGE: case IEEE80211_AUTH_SHARED_RESPONSE: if (challenge == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "%s", "no challenge"); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (challenge[1] != IEEE80211_CHALLENGE_LEN) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad challenge len %d", challenge[1]); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } default: break; } if (vap->iv_state != IEEE80211_S_AUTH) return; switch (seq) { case IEEE80211_AUTH_SHARED_PASS: if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } if (status != 0) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, wh, "shared key auth failed (reason %d)", status); vap->iv_stats.is_rx_auth_fail++; vap->iv_stats.is_rx_authfail_code = status; return; } ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); break; case IEEE80211_AUTH_SHARED_CHALLENGE: if (!ieee80211_alloc_challenge(ni)) return; /* XXX could optimize by passing recvd challenge */ memcpy(ni->ni_challenge, &challenge[2], challenge[1]); IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH, wh, "shared key auth", "bad seq %d", seq); vap->iv_stats.is_rx_bad_auth++; return; } return; bad: /* * Kick the state machine. This short-circuits * using the mgt frame timeout to trigger the * state transition. */ if (vap->iv_state == IEEE80211_S_AUTH) ieee80211_new_state(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_STATUS); } int ieee80211_parse_wmeparams(struct ieee80211vap *vap, uint8_t *frm, const struct ieee80211_frame *wh) { #define MS(_v, _f) (((_v) & _f) >> _f##_S) struct ieee80211_wme_state *wme = &vap->iv_ic->ic_wme; u_int len = frm[1], qosinfo; int i; if (len < sizeof(struct ieee80211_wme_param)-2) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME, wh, "WME", "too short, len %u", len); return -1; } qosinfo = frm[__offsetof(struct ieee80211_wme_param, param_qosInfo)]; qosinfo &= WME_QOSINFO_COUNT; /* XXX do proper check for wraparound */ if (qosinfo == wme->wme_wmeChanParams.cap_info) return 0; frm += __offsetof(struct ieee80211_wme_param, params_acParams); for (i = 0; i < WME_NUM_AC; i++) { struct wmeParams *wmep = &wme->wme_wmeChanParams.cap_wmeParams[i]; /* NB: ACI not used */ wmep->wmep_acm = MS(frm[0], WME_PARAM_ACM); wmep->wmep_aifsn = MS(frm[0], WME_PARAM_AIFSN); wmep->wmep_logcwmin = MS(frm[1], WME_PARAM_LOGCWMIN); wmep->wmep_logcwmax = MS(frm[1], WME_PARAM_LOGCWMAX); wmep->wmep_txopLimit = LE_READ_2(frm+2); frm += 4; } wme->wme_wmeChanParams.cap_info = qosinfo; return 1; #undef MS } /* * Process 11h Channel Switch Announcement (CSA) ie. If this * is the first CSA then initiate the switch. Otherwise we * track state and trigger completion and/or cancel of the switch. * XXX should be public for IBSS use */ static void ieee80211_parse_csaparams(struct ieee80211vap *vap, uint8_t *frm, const struct ieee80211_frame *wh) { struct ieee80211com *ic = vap->iv_ic; const struct ieee80211_csa_ie *csa = (const struct ieee80211_csa_ie *) frm; KASSERT(vap->iv_state >= IEEE80211_S_RUN, ("state %s", ieee80211_state_name[vap->iv_state])); if (csa->csa_mode > 1) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, wh, "CSA", "invalid mode %u", csa->csa_mode); return; } IEEE80211_LOCK(ic); if ((ic->ic_flags & IEEE80211_F_CSAPENDING) == 0) { /* * Convert the channel number to a channel reference. We * try first to preserve turbo attribute of the current * channel then fallback. Note this will not work if the * CSA specifies a channel that requires a band switch (e.g. * 11a => 11g). This is intentional as 11h is defined only * for 5GHz/11a and because the switch does not involve a * reassociation, protocol state (capabilities, negotated * rates, etc) may/will be wrong. */ struct ieee80211_channel *c = ieee80211_find_channel_byieee(ic, csa->csa_newchan, (ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALLTURBO)); if (c == NULL) { c = ieee80211_find_channel_byieee(ic, csa->csa_newchan, (ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALL)); if (c == NULL) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, wh, "CSA", "invalid channel %u", csa->csa_newchan); goto done; } } #if IEEE80211_CSA_COUNT_MIN > 0 if (csa->csa_count < IEEE80211_CSA_COUNT_MIN) { /* * Require at least IEEE80211_CSA_COUNT_MIN count to * reduce the risk of being redirected by a fabricated * CSA. If a valid CSA is dropped we'll still get a * beacon miss when the AP leaves the channel so we'll * eventually follow to the new channel. * * NOTE: this violates the 11h spec that states that * count may be any value and if 0 then a switch * should happen asap. */ IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, wh, "CSA", "count %u too small, must be >= %u", csa->csa_count, IEEE80211_CSA_COUNT_MIN); goto done; } #endif ieee80211_csa_startswitch(ic, c, csa->csa_mode, csa->csa_count); } else { /* * Validate this ie against the initial CSA. We require * mode and channel not change and the count must be * monotonically decreasing. This may be pointless and * canceling the switch as a result may be too paranoid but * in the worst case if we drop out of CSA because of this * and the AP does move then we'll just end up taking a * beacon miss and scan to find the AP. * * XXX may want <= on count as we also process ProbeResp * frames and those may come in w/ the same count as the * previous beacon; but doing so leaves us open to a stuck * count until we add a dead-man timer */ if (!(csa->csa_count < ic->ic_csa_count && csa->csa_mode == ic->ic_csa_mode && csa->csa_newchan == ieee80211_chan2ieee(ic, ic->ic_csa_newchan))) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_DOTH, wh, "CSA ie mismatch, initial ie <%d,%d,%d>, " "this ie <%d,%d,%d>", ic->ic_csa_mode, ic->ic_csa_newchan, ic->ic_csa_count, csa->csa_mode, csa->csa_newchan, csa->csa_count); ieee80211_csa_cancelswitch(ic); } else { if (csa->csa_count <= 1) ieee80211_csa_completeswitch(ic); else ic->ic_csa_count = csa->csa_count; } } done: IEEE80211_UNLOCK(ic); } /* * Return non-zero if a background scan may be continued: * o bg scan is active * o no channel switch is pending * o there has not been any traffic recently * * Note we do not check if there is an administrative enable; * this is only done to start the scan. We assume that any * change in state will be accompanied by a request to cancel * active scans which will otherwise cause this test to fail. */ static __inline int contbgscan(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; return ((ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN) && (ic->ic_flags & IEEE80211_F_CSAPENDING) == 0 && vap->iv_state == IEEE80211_S_RUN && /* XXX? */ ieee80211_time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle)); } /* * Return non-zero if a backgrond scan may be started: * o bg scanning is administratively enabled * o no channel switch is pending * o we are not boosted on a dynamic turbo channel * o there has not been a scan recently * o there has not been any traffic recently */ static __inline int startbgscan(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; return ((vap->iv_flags & IEEE80211_F_BGSCAN) && (ic->ic_flags & IEEE80211_F_CSAPENDING) == 0 && #ifdef IEEE80211_SUPPORT_SUPERG !IEEE80211_IS_CHAN_DTURBO(ic->ic_curchan) && #endif ieee80211_time_after(ticks, ic->ic_lastscan + vap->iv_bgscanintvl) && ieee80211_time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle)); } static void sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { #define ISPROBE(_st) ((_st) == IEEE80211_FC0_SUBTYPE_PROBE_RESP) #define ISREASSOC(_st) ((_st) == IEEE80211_FC0_SUBTYPE_REASSOC_RESP) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_channel *rxchan = ic->ic_curchan; struct ieee80211_frame *wh; uint8_t *frm, *efrm; uint8_t *rates, *xrates, *wme, *htcap, *htinfo; uint8_t rate; int ht_state_change = 0; wh = mtod(m0, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; efrm = mtod(m0, uint8_t *) + m0->m_len; switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: case IEEE80211_FC0_SUBTYPE_BEACON: { struct ieee80211_scanparams scan; struct ieee80211_channel *c; /* * We process beacon/probe response frames: * o when scanning, or * o station mode when associated (to collect state * updates such as 802.11g slot time) * Frames otherwise received are discarded. */ if (!((ic->ic_flags & IEEE80211_F_SCAN) || ni->ni_associd)) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* Override RX channel as appropriate */ if (rxs != NULL) { c = ieee80211_lookup_channel_rxstatus(vap, rxs); if (c != NULL) rxchan = c; } /* XXX probe response in sta mode when !scanning? */ if (ieee80211_parse_beacon(ni, m0, rxchan, &scan) != 0) { if (! (ic->ic_flags & IEEE80211_F_SCAN)) vap->iv_stats.is_beacon_bad++; return; } /* * Count frame now that we know it's to be processed. */ if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { vap->iv_stats.is_rx_beacon++; /* XXX remove */ IEEE80211_NODE_STAT(ni, rx_beacons); } else IEEE80211_NODE_STAT(ni, rx_proberesp); /* * When operating in station mode, check for state updates. * Be careful to ignore beacons received while doing a * background scan. We consider only 11g/WMM stuff right now. */ if (ni->ni_associd != 0 && ((ic->ic_flags & IEEE80211_F_SCAN) == 0 || IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid))) { /* record tsf of last beacon */ memcpy(ni->ni_tstamp.data, scan.tstamp, sizeof(ni->ni_tstamp)); /* count beacon frame for s/w bmiss handling */ vap->iv_swbmiss_count++; vap->iv_bmiss_count = 0; if (ni->ni_erp != scan.erp) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "erp change: was 0x%x, now 0x%x", ni->ni_erp, scan.erp); if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) ic->ic_flags |= IEEE80211_F_USEPROT; else ic->ic_flags &= ~IEEE80211_F_USEPROT; ni->ni_erp = scan.erp; /* XXX statistic */ /* XXX driver notification */ } if ((ni->ni_capinfo ^ scan.capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "capabilities change: was 0x%x, now 0x%x", ni->ni_capinfo, scan.capinfo); /* * NB: we assume short preamble doesn't * change dynamically */ ieee80211_set_shortslottime(ic, IEEE80211_IS_CHAN_A(ic->ic_bsschan) || (scan.capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); ni->ni_capinfo = (ni->ni_capinfo &~ IEEE80211_CAPINFO_SHORT_SLOTTIME) | (scan.capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME); /* XXX statistic */ } if (scan.wme != NULL && (ni->ni_flags & IEEE80211_NODE_QOS) && ieee80211_parse_wmeparams(vap, scan.wme, wh) > 0) ieee80211_wme_updateparams(vap); #ifdef IEEE80211_SUPPORT_SUPERG if (scan.ath != NULL) ieee80211_parse_athparams(ni, scan.ath, wh); #endif if (scan.htcap != NULL && scan.htinfo != NULL && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { /* XXX state changes? */ if (ieee80211_ht_updateparams(ni, scan.htcap, scan.htinfo)) ht_state_change = 1; } if (scan.quiet) ic->ic_set_quiet(ni, scan.quiet); if (scan.tim != NULL) { struct ieee80211_tim_ie *tim = (struct ieee80211_tim_ie *) scan.tim; /* * XXX Check/debug this code; see if it's about * the right time to force the VAP awake if we * receive a frame destined for us? */ int aid = IEEE80211_AID(ni->ni_associd); int ix = aid / NBBY; int min = tim->tim_bitctl &~ 1; int max = tim->tim_len + min - 4; int tim_ucast = 0, tim_mcast = 0; /* * Only do this for unicast traffic in the TIM * The multicast traffic notification for * the scan notification stuff should occur * differently. */ if (min <= ix && ix <= max && isset(tim->tim_bitmap - min, aid)) { tim_ucast = 1; } /* * Do a separate notification * for the multicast bit being set. */ if (tim->tim_bitctl & 1) { tim_mcast = 1; } /* * If the TIM indicates there's traffic for * us then get us out of STA mode powersave. */ if (tim_ucast == 1) { /* * Wake us out of SLEEP state if we're * in it; and if we're doing bgscan * then wake us out of STA powersave. */ ieee80211_sta_tim_notify(vap, 1); /* * This is preventing us from * continuing a bgscan; because it * tricks the contbgscan() * routine to think there's always * traffic for us. * * I think we need both an RX and * TX ic_lastdata field. */ ic->ic_lastdata = ticks; } ni->ni_dtim_count = tim->tim_count; ni->ni_dtim_period = tim->tim_period; } if (scan.csa != NULL && (vap->iv_flags & IEEE80211_F_DOTH)) ieee80211_parse_csaparams(vap, scan.csa, wh); else if (ic->ic_flags & IEEE80211_F_CSAPENDING) { /* * No CSA ie or 11h disabled, but a channel * switch is pending; drop out so we aren't * stuck in CSA state. If the AP really is * moving we'll get a beacon miss and scan. */ IEEE80211_LOCK(ic); ieee80211_csa_cancelswitch(ic); IEEE80211_UNLOCK(ic); } /* * If scanning, pass the info to the scan module. * Otherwise, check if it's the right time to do * a background scan. Background scanning must * be enabled and we must not be operating in the * turbo phase of dynamic turbo mode. Then, * it's been a while since the last background * scan and if no data frames have come through * recently, kick off a scan. Note that this * is the mechanism by which a background scan * is started _and_ continued each time we * return on-channel to receive a beacon from * our ap. */ if (ic->ic_flags & IEEE80211_F_SCAN) { ieee80211_add_scan(vap, rxchan, &scan, wh, subtype, rssi, nf); } else if (contbgscan(vap)) { ieee80211_bg_scan(vap, 0); } else if (startbgscan(vap)) { vap->iv_stats.is_scan_bg++; #if 0 /* wakeup if we are sleeing */ ieee80211_set_pwrsave(vap, 0); #endif ieee80211_bg_scan(vap, 0); } /* * Put the station to sleep if we haven't seen * traffic in a while. */ IEEE80211_LOCK(ic); ieee80211_sta_ps_timer_check(vap); IEEE80211_UNLOCK(ic); /* * If we've had a channel width change (eg HT20<->HT40) * then schedule a delayed driver notification. */ if (ht_state_change) ieee80211_update_chw(ic); return; } /* * If scanning, just pass information to the scan module. */ if (ic->ic_flags & IEEE80211_F_SCAN) { if (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN) { /* * Actively scanning a channel marked passive; * send a probe request now that we know there * is 802.11 traffic present. * * XXX check if the beacon we recv'd gives * us what we need and suppress the probe req */ ieee80211_probe_curchan(vap, 1); ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN; } ieee80211_add_scan(vap, rxchan, &scan, wh, subtype, rssi, nf); return; } break; } case IEEE80211_FC0_SUBTYPE_AUTH: { uint16_t algo, seq, status; /* * auth frame format * [2] algorithm * [2] sequence * [2] status * [tlv*] challenge */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); algo = le16toh(*(uint16_t *)frm); seq = le16toh(*(uint16_t *)(frm + 2)); status = le16toh(*(uint16_t *)(frm + 4)); IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr2, "recv auth frame with algorithm %d seq %d", algo, seq); if (vap->iv_flags & IEEE80211_F_COUNTERM) { IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH | IEEE80211_MSG_CRYPTO, wh, "auth", "%s", "TKIP countermeasures enabled"); vap->iv_stats.is_rx_auth_countermeasures++; if (vap->iv_opmode == IEEE80211_M_HOSTAP) { ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, IEEE80211_REASON_MIC_FAILURE); } return; } if (algo == IEEE80211_AUTH_ALG_SHARED) sta_auth_shared(ni, wh, frm + 6, efrm, rssi, nf, seq, status); else if (algo == IEEE80211_AUTH_ALG_OPEN) sta_auth_open(ni, wh, rssi, nf, seq, status); else { IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "auth", "unsupported alg %d", algo); vap->iv_stats.is_rx_auth_unsupported++; return; } break; } case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: { uint16_t capinfo, associd; uint16_t status; if (vap->iv_state != IEEE80211_S_ASSOC) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* * asresp frame format * [2] capability information * [2] status * [2] association ID * [tlv] supported rates * [tlv] extended supported rates * [tlv] WME * [tlv] HT capabilities * [tlv] HT info */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); ni = vap->iv_bss; capinfo = le16toh(*(uint16_t *)frm); frm += 2; status = le16toh(*(uint16_t *)frm); frm += 2; if (status != 0) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "%sassoc failed (reason %d)", ISREASSOC(subtype) ? "re" : "", status); vap->iv_stats.is_rx_auth_fail++; /* XXX */ return; } associd = le16toh(*(uint16_t *)frm); frm += 2; rates = xrates = wme = htcap = htinfo = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); switch (*frm) { case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; case IEEE80211_ELEMID_HTCAP: htcap = frm; break; case IEEE80211_ELEMID_HTINFO: htinfo = frm; break; case IEEE80211_ELEMID_VENDOR: if (iswmeoui(frm)) wme = frm; else if (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) { /* * Accept pre-draft HT ie's if the * standard ones have not been seen. */ if (ishtcapoui(frm)) { if (htcap == NULL) htcap = frm; } else if (ishtinfooui(frm)) { if (htinfo == NULL) htinfo = frm; } } /* XXX Atheros OUI support */ break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); if (xrates != NULL) IEEE80211_VERIFY_ELEMENT(xrates, IEEE80211_RATE_MAXSIZE - rates[1], return); rate = ieee80211_setup_rates(ni, rates, xrates, IEEE80211_F_JOIN | IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); if (rate & IEEE80211_RATE_BASIC) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "%sassoc failed (rate set mismatch)", ISREASSOC(subtype) ? "re" : ""); vap->iv_stats.is_rx_assoc_norate++; ieee80211_new_state(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_STATUS); return; } ni->ni_capinfo = capinfo; ni->ni_associd = associd; if (ni->ni_jointime == 0) ni->ni_jointime = time_uptime; if (wme != NULL && ieee80211_parse_wmeparams(vap, wme, wh) >= 0) { ni->ni_flags |= IEEE80211_NODE_QOS; ieee80211_wme_updateparams(vap); } else ni->ni_flags &= ~IEEE80211_NODE_QOS; /* * Setup HT state according to the negotiation. * * NB: shouldn't need to check if HT use is enabled but some * ap's send back HT ie's even when we don't indicate we * are HT capable in our AssocReq. */ if (htcap != NULL && htinfo != NULL && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { ieee80211_ht_node_init(ni); ieee80211_ht_updateparams(ni, htcap, htinfo); ieee80211_setup_htrates(ni, htcap, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, htinfo); ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); - } else { -#ifdef IEEE80211_SUPPORT_SUPERG - if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_ATH)) - ieee80211_ff_node_init(ni); -#endif } + /* * Configure state now that we are associated. * * XXX may need different/additional driver callbacks? */ if (IEEE80211_IS_CHAN_A(ic->ic_curchan) || (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE)) { ic->ic_flags |= IEEE80211_F_SHPREAMBLE; ic->ic_flags &= ~IEEE80211_F_USEBARKER; } else { ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE; ic->ic_flags |= IEEE80211_F_USEBARKER; } ieee80211_set_shortslottime(ic, IEEE80211_IS_CHAN_A(ic->ic_curchan) || (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); /* * Honor ERP protection. * * NB: ni_erp should zero for non-11g operation. */ if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) ic->ic_flags |= IEEE80211_F_USEPROT; else ic->ic_flags &= ~IEEE80211_F_USEPROT; IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, wh->i_addr2, "%sassoc success at aid %d: %s preamble, %s slot time%s%s%s%s%s%s%s%s", ISREASSOC(subtype) ? "re" : "", IEEE80211_NODE_AID(ni), ic->ic_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long", ic->ic_flags&IEEE80211_F_SHSLOT ? "short" : "long", ic->ic_flags&IEEE80211_F_USEPROT ? ", protection" : "", ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "", ni->ni_flags & IEEE80211_NODE_HT ? (ni->ni_chw == 40 ? ", HT40" : ", HT20") : "", ni->ni_flags & IEEE80211_NODE_AMPDU ? " (+AMPDU)" : "", ni->ni_flags & IEEE80211_NODE_MIMO_RTS ? " (+SMPS-DYN)" : ni->ni_flags & IEEE80211_NODE_MIMO_PS ? " (+SMPS)" : "", ni->ni_flags & IEEE80211_NODE_RIFS ? " (+RIFS)" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF) ? ", fast-frames" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_TURBOP) ? ", turbo" : "" ); ieee80211_new_state(vap, IEEE80211_S_RUN, subtype); break; } case IEEE80211_FC0_SUBTYPE_DEAUTH: { uint16_t reason; if (vap->iv_state == IEEE80211_S_SCAN) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { /* NB: can happen when in promiscuous mode */ vap->iv_stats.is_rx_mgtdiscard++; break; } /* * deauth frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); reason = le16toh(*(uint16_t *)frm); vap->iv_stats.is_rx_deauth++; vap->iv_stats.is_rx_deauth_code = reason; IEEE80211_NODE_STAT(ni, rx_deauth); IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "recv deauthenticate (reason %d)", reason); ieee80211_new_state(vap, IEEE80211_S_AUTH, (reason << 8) | IEEE80211_FC0_SUBTYPE_DEAUTH); break; } case IEEE80211_FC0_SUBTYPE_DISASSOC: { uint16_t reason; if (vap->iv_state != IEEE80211_S_RUN && vap->iv_state != IEEE80211_S_ASSOC && vap->iv_state != IEEE80211_S_AUTH) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { /* NB: can happen when in promiscuous mode */ vap->iv_stats.is_rx_mgtdiscard++; break; } /* * disassoc frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); reason = le16toh(*(uint16_t *)frm); vap->iv_stats.is_rx_disassoc++; vap->iv_stats.is_rx_disassoc_code = reason; IEEE80211_NODE_STAT(ni, rx_disassoc); IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, "recv disassociate (reason %d)", reason); ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); break; } case IEEE80211_FC0_SUBTYPE_ACTION: case IEEE80211_FC0_SUBTYPE_ACTION_NOACK: if (!IEEE80211_ADDR_EQ(vap->iv_myaddr, wh->i_addr1) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not for us"); vap->iv_stats.is_rx_mgtdiscard++; } else if (vap->iv_state != IEEE80211_S_RUN) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "wrong state %s", ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_rx_mgtdiscard++; } else { if (ieee80211_parse_action(ni, m0) == 0) (void)ic->ic_recv_action(ni, wh, frm, efrm); } break; case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: case IEEE80211_FC0_SUBTYPE_PROBE_REQ: case IEEE80211_FC0_SUBTYPE_TIMING_ADV: case IEEE80211_FC0_SUBTYPE_ATIM: IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not handled"); vap->iv_stats.is_rx_mgtdiscard++; break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "mgt", "subtype 0x%x not handled", subtype); vap->iv_stats.is_rx_badsubtype++; break; } #undef ISREASSOC #undef ISPROBE } static void sta_recv_ctl(struct ieee80211_node *ni, struct mbuf *m, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_BAR: ieee80211_recv_bar(ni, m); break; } } Index: projects/release-pkg/sys/net80211/ieee80211_superg.c =================================================================== --- projects/release-pkg/sys/net80211/ieee80211_superg.c (revision 297604) +++ projects/release-pkg/sys/net80211/ieee80211_superg.c (revision 297605) @@ -1,903 +1,1051 @@ /*- * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting * 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 "opt_wlan.h" #ifdef IEEE80211_SUPPORT_SUPERG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Atheros fast-frame encapsulation format. * FF max payload: * 802.2 + FFHDR + HPAD + 802.3 + 802.2 + 1500 + SPAD + 802.3 + 802.2 + 1500: * 8 + 4 + 4 + 14 + 8 + 1500 + 6 + 14 + 8 + 1500 * = 3066 */ /* fast frame header is 32-bits */ #define ATH_FF_PROTO 0x0000003f /* protocol */ #define ATH_FF_PROTO_S 0 #define ATH_FF_FTYPE 0x000000c0 /* frame type */ #define ATH_FF_FTYPE_S 6 #define ATH_FF_HLEN32 0x00000300 /* optional hdr length */ #define ATH_FF_HLEN32_S 8 #define ATH_FF_SEQNUM 0x001ffc00 /* sequence number */ #define ATH_FF_SEQNUM_S 10 #define ATH_FF_OFFSET 0xffe00000 /* offset to 2nd payload */ #define ATH_FF_OFFSET_S 21 #define ATH_FF_MAX_HDR_PAD 4 #define ATH_FF_MAX_SEP_PAD 6 #define ATH_FF_MAX_HDR 30 #define ATH_FF_PROTO_L2TUNNEL 0 /* L2 tunnel protocol */ #define ATH_FF_ETH_TYPE 0x88bd /* Ether type for encapsulated frames */ #define ATH_FF_SNAP_ORGCODE_0 0x00 #define ATH_FF_SNAP_ORGCODE_1 0x03 #define ATH_FF_SNAP_ORGCODE_2 0x7f #define ATH_FF_TXQMIN 2 /* min txq depth for staging */ #define ATH_FF_TXQMAX 50 /* maximum # of queued frames allowed */ #define ATH_FF_STAGEMAX 5 /* max waiting period for staged frame*/ #define ETHER_HEADER_COPY(dst, src) \ memcpy(dst, src, sizeof(struct ether_header)) static int ieee80211_ffppsmin = 2; /* pps threshold for ff aggregation */ SYSCTL_INT(_net_wlan, OID_AUTO, ffppsmin, CTLFLAG_RW, &ieee80211_ffppsmin, 0, "min packet rate before fast-frame staging"); static int ieee80211_ffagemax = -1; /* max time frames held on stage q */ SYSCTL_PROC(_net_wlan, OID_AUTO, ffagemax, CTLTYPE_INT | CTLFLAG_RW, &ieee80211_ffagemax, 0, ieee80211_sysctl_msecs_ticks, "I", "max hold time for fast-frame staging (ms)"); void ieee80211_superg_attach(struct ieee80211com *ic) { struct ieee80211_superg *sg; - if (ic->ic_caps & IEEE80211_C_FF) { - sg = (struct ieee80211_superg *) IEEE80211_MALLOC( - sizeof(struct ieee80211_superg), M_80211_VAP, - IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); - if (sg == NULL) { - printf("%s: cannot allocate SuperG state block\n", - __func__); - return; - } - ic->ic_superg = sg; + sg = (struct ieee80211_superg *) IEEE80211_MALLOC( + sizeof(struct ieee80211_superg), M_80211_VAP, + IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); + if (sg == NULL) { + printf("%s: cannot allocate SuperG state block\n", + __func__); + return; } - ieee80211_ffagemax = msecs_to_ticks(150); + ic->ic_superg = sg; + + /* + * Default to not being so aggressive for FF/AMSDU + * aging, otherwise we may hold a frame around + * for way too long before we expire it out. + */ + ieee80211_ffagemax = msecs_to_ticks(2); } void ieee80211_superg_detach(struct ieee80211com *ic) { if (ic->ic_superg != NULL) { IEEE80211_FREE(ic->ic_superg, M_80211_VAP); ic->ic_superg = NULL; } } void ieee80211_superg_vattach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; if (ic->ic_superg == NULL) /* NB: can't do fast-frames w/o state */ vap->iv_caps &= ~IEEE80211_C_FF; if (vap->iv_caps & IEEE80211_C_FF) vap->iv_flags |= IEEE80211_F_FF; /* NB: we only implement sta mode */ if (vap->iv_opmode == IEEE80211_M_STA && (vap->iv_caps & IEEE80211_C_TURBOP)) vap->iv_flags |= IEEE80211_F_TURBOP; } void ieee80211_superg_vdetach(struct ieee80211vap *vap) { } #define ATH_OUI_BYTES 0x00, 0x03, 0x7f /* * Add a WME information element to a frame. */ uint8_t * ieee80211_add_ath(uint8_t *frm, uint8_t caps, ieee80211_keyix defkeyix) { static const struct ieee80211_ath_ie info = { .ath_id = IEEE80211_ELEMID_VENDOR, .ath_len = sizeof(struct ieee80211_ath_ie) - 2, .ath_oui = { ATH_OUI_BYTES }, .ath_oui_type = ATH_OUI_TYPE, .ath_oui_subtype= ATH_OUI_SUBTYPE, .ath_version = ATH_OUI_VERSION, }; struct ieee80211_ath_ie *ath = (struct ieee80211_ath_ie *) frm; memcpy(frm, &info, sizeof(info)); ath->ath_capability = caps; if (defkeyix != IEEE80211_KEYIX_NONE) { ath->ath_defkeyix[0] = (defkeyix & 0xff); ath->ath_defkeyix[1] = ((defkeyix >> 8) & 0xff); } else { ath->ath_defkeyix[0] = 0xff; ath->ath_defkeyix[1] = 0x7f; } return frm + sizeof(info); } #undef ATH_OUI_BYTES uint8_t * ieee80211_add_athcaps(uint8_t *frm, const struct ieee80211_node *bss) { const struct ieee80211vap *vap = bss->ni_vap; return ieee80211_add_ath(frm, vap->iv_flags & IEEE80211_F_ATHEROS, ((vap->iv_flags & IEEE80211_F_WPA) == 0 && bss->ni_authmode != IEEE80211_AUTH_8021X) ? vap->iv_def_txkey : IEEE80211_KEYIX_NONE); } void ieee80211_parse_ath(struct ieee80211_node *ni, uint8_t *ie) { const struct ieee80211_ath_ie *ath = (const struct ieee80211_ath_ie *) ie; ni->ni_ath_flags = ath->ath_capability; ni->ni_ath_defkeyix = LE_READ_2(&ath->ath_defkeyix); } int ieee80211_parse_athparams(struct ieee80211_node *ni, uint8_t *frm, const struct ieee80211_frame *wh) { struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_ath_ie *ath; u_int len = frm[1]; int capschanged; uint16_t defkeyix; if (len < sizeof(struct ieee80211_ath_ie)-2) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_SUPERG, wh, "Atheros", "too short, len %u", len); return -1; } ath = (const struct ieee80211_ath_ie *)frm; capschanged = (ni->ni_ath_flags != ath->ath_capability); defkeyix = LE_READ_2(ath->ath_defkeyix); if (capschanged || defkeyix != ni->ni_ath_defkeyix) { ni->ni_ath_flags = ath->ath_capability; ni->ni_ath_defkeyix = defkeyix; IEEE80211_NOTE(vap, IEEE80211_MSG_SUPERG, ni, "ath ie change: new caps 0x%x defkeyix 0x%x", ni->ni_ath_flags, ni->ni_ath_defkeyix); } if (IEEE80211_ATH_CAP(vap, ni, ATHEROS_CAP_TURBO_PRIME)) { uint16_t curflags, newflags; /* * Check for turbo mode switch. Calculate flags * for the new mode and effect the switch. */ newflags = curflags = vap->iv_ic->ic_bsschan->ic_flags; /* NB: BOOST is not in ic_flags, so get it from the ie */ if (ath->ath_capability & ATHEROS_CAP_BOOST) newflags |= IEEE80211_CHAN_TURBO; else newflags &= ~IEEE80211_CHAN_TURBO; if (newflags != curflags) ieee80211_dturbo_switch(vap, newflags); } return capschanged; } /* * Decap the encapsulated frame pair and dispatch the first * for delivery. The second frame is returned for delivery * via the normal path. */ struct mbuf * ieee80211_ff_decap(struct ieee80211_node *ni, struct mbuf *m) { #define FF_LLC_SIZE (sizeof(struct ether_header) + sizeof(struct llc)) #define MS(x,f) (((x) & f) >> f##_S) struct ieee80211vap *vap = ni->ni_vap; struct llc *llc; uint32_t ath; struct mbuf *n; int framelen; /* NB: we assume caller does this check for us */ KASSERT(IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF), ("ff not negotiated")); /* * Check for fast-frame tunnel encapsulation. */ if (m->m_pkthdr.len < 3*FF_LLC_SIZE) return m; if (m->m_len < FF_LLC_SIZE && (m = m_pullup(m, FF_LLC_SIZE)) == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "fast-frame", "%s", "m_pullup(llc) failed"); vap->iv_stats.is_rx_tooshort++; return NULL; } llc = (struct llc *)(mtod(m, uint8_t *) + sizeof(struct ether_header)); if (llc->llc_snap.ether_type != htons(ATH_FF_ETH_TYPE)) return m; m_adj(m, FF_LLC_SIZE); m_copydata(m, 0, sizeof(uint32_t), (caddr_t) &ath); if (MS(ath, ATH_FF_PROTO) != ATH_FF_PROTO_L2TUNNEL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "fast-frame", "unsupport tunnel protocol, header 0x%x", ath); vap->iv_stats.is_ff_badhdr++; m_freem(m); return NULL; } /* NB: skip header and alignment padding */ m_adj(m, roundup(sizeof(uint32_t) - 2, 4) + 2); vap->iv_stats.is_ff_decap++; /* * Decap the first frame, bust it apart from the * second and deliver; then decap the second frame * and return it to the caller for normal delivery. */ m = ieee80211_decap1(m, &framelen); if (m == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "fast-frame", "%s", "first decap failed"); vap->iv_stats.is_ff_tooshort++; return NULL; } n = m_split(m, framelen, M_NOWAIT); if (n == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "fast-frame", "%s", "unable to split encapsulated frames"); vap->iv_stats.is_ff_split++; m_freem(m); /* NB: must reclaim */ return NULL; } /* XXX not right for WDS */ vap->iv_deliver_data(vap, ni, m); /* 1st of pair */ /* * Decap second frame. */ m_adj(n, roundup2(framelen, 4) - framelen); /* padding */ n = ieee80211_decap1(n, &framelen); if (n == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "fast-frame", "%s", "second decap failed"); vap->iv_stats.is_ff_tooshort++; } /* XXX verify framelen against mbuf contents */ return n; /* 2nd delivered by caller */ #undef MS #undef FF_LLC_SIZE } /* * Fast frame encapsulation. There must be two packets * chained with m_nextpkt. We do header adjustment for * each, add the tunnel encapsulation, and then concatenate * the mbuf chains to form a single frame for transmission. */ struct mbuf * ieee80211_ff_encap(struct ieee80211vap *vap, struct mbuf *m1, int hdrspace, struct ieee80211_key *key) { struct mbuf *m2; struct ether_header eh1, eh2; struct llc *llc; struct mbuf *m; int pad; m2 = m1->m_nextpkt; if (m2 == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: only one frame\n", __func__); goto bad; } m1->m_nextpkt = NULL; + /* - * Include fast frame headers in adjusting header layout. + * Adjust to include 802.11 header requirement. */ KASSERT(m1->m_len >= sizeof(eh1), ("no ethernet header!")); ETHER_HEADER_COPY(&eh1, mtod(m1, caddr_t)); - m1 = ieee80211_mbuf_adjust(vap, - hdrspace + sizeof(struct llc) + sizeof(uint32_t) + 2 + - sizeof(struct ether_header), - key, m1); + m1 = ieee80211_mbuf_adjust(vap, hdrspace, key, m1); if (m1 == NULL) { + printf("%s: failed initial mbuf_adjust\n", __func__); /* NB: ieee80211_mbuf_adjust handles msgs+statistics */ m_freem(m2); goto bad; } /* * Copy second frame's Ethernet header out of line - * and adjust for encapsulation headers. Note that - * we make room for padding in case there isn't room + * and adjust for possible padding in case there isn't room * at the end of first frame. */ KASSERT(m2->m_len >= sizeof(eh2), ("no ethernet header!")); ETHER_HEADER_COPY(&eh2, mtod(m2, caddr_t)); - m2 = ieee80211_mbuf_adjust(vap, - ATH_FF_MAX_HDR_PAD + sizeof(struct ether_header), - NULL, m2); + m2 = ieee80211_mbuf_adjust(vap, 4, NULL, m2); if (m2 == NULL) { /* NB: ieee80211_mbuf_adjust handles msgs+statistics */ + printf("%s: failed second \n", __func__); goto bad; } /* * Now do tunnel encapsulation. First, each * frame gets a standard encapsulation. */ m1 = ieee80211_ff_encap1(vap, m1, &eh1); if (m1 == NULL) goto bad; m2 = ieee80211_ff_encap1(vap, m2, &eh2); if (m2 == NULL) goto bad; /* * Pad leading frame to a 4-byte boundary. If there * is space at the end of the first frame, put it * there; otherwise prepend to the front of the second * frame. We know doing the second will always work * because we reserve space above. We prefer appending * as this typically has better DMA alignment properties. */ for (m = m1; m->m_next != NULL; m = m->m_next) ; pad = roundup2(m1->m_pkthdr.len, 4) - m1->m_pkthdr.len; if (pad) { if (M_TRAILINGSPACE(m) < pad) { /* prepend to second */ m2->m_data -= pad; m2->m_len += pad; m2->m_pkthdr.len += pad; } else { /* append to first */ m->m_len += pad; m1->m_pkthdr.len += pad; } } /* - * Now, stick 'em together and prepend the tunnel headers; - * first the Atheros tunnel header (all zero for now) and - * then a special fast frame LLC. + * A-MSDU's are just appended; the "I'm A-MSDU!" bit is in the + * QoS header. * * XXX optimize by prepending together */ m->m_next = m2; /* NB: last mbuf from above */ m1->m_pkthdr.len += m2->m_pkthdr.len; M_PREPEND(m1, sizeof(uint32_t)+2, M_NOWAIT); if (m1 == NULL) { /* XXX cannot happen */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: no space for tunnel header\n", __func__); vap->iv_stats.is_tx_nobuf++; return NULL; } memset(mtod(m1, void *), 0, sizeof(uint32_t)+2); M_PREPEND(m1, sizeof(struct llc), M_NOWAIT); if (m1 == NULL) { /* XXX cannot happen */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: no space for llc header\n", __func__); vap->iv_stats.is_tx_nobuf++; return NULL; } llc = mtod(m1, struct llc *); llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP; llc->llc_control = LLC_UI; llc->llc_snap.org_code[0] = ATH_FF_SNAP_ORGCODE_0; llc->llc_snap.org_code[1] = ATH_FF_SNAP_ORGCODE_1; llc->llc_snap.org_code[2] = ATH_FF_SNAP_ORGCODE_2; llc->llc_snap.ether_type = htons(ATH_FF_ETH_TYPE); vap->iv_stats.is_ff_encap++; return m1; bad: + vap->iv_stats.is_ff_encapfail++; if (m1 != NULL) m_freem(m1); if (m2 != NULL) m_freem(m2); return NULL; } +/* + * A-MSDU encapsulation. + * + * This assumes just two frames for now, since we're borrowing the + * same queuing code and infrastructure as fast-frames. + * + * There must be two packets chained with m_nextpkt. + * We do header adjustment for each, and then concatenate the mbuf chains + * to form a single frame for transmission. + */ +struct mbuf * +ieee80211_amsdu_encap(struct ieee80211vap *vap, struct mbuf *m1, int hdrspace, + struct ieee80211_key *key) +{ + struct mbuf *m2; + struct ether_header eh1, eh2; + struct mbuf *m; + int pad; + + m2 = m1->m_nextpkt; + if (m2 == NULL) { + IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, + "%s: only one frame\n", __func__); + goto bad; + } + m1->m_nextpkt = NULL; + + /* + * Include A-MSDU header in adjusting header layout. + */ + KASSERT(m1->m_len >= sizeof(eh1), ("no ethernet header!")); + ETHER_HEADER_COPY(&eh1, mtod(m1, caddr_t)); + m1 = ieee80211_mbuf_adjust(vap, + hdrspace + sizeof(struct llc) + sizeof(uint32_t) + + sizeof(struct ether_header), + key, m1); + if (m1 == NULL) { + /* NB: ieee80211_mbuf_adjust handles msgs+statistics */ + m_freem(m2); + goto bad; + } + + /* + * Copy second frame's Ethernet header out of line + * and adjust for encapsulation headers. Note that + * we make room for padding in case there isn't room + * at the end of first frame. + */ + KASSERT(m2->m_len >= sizeof(eh2), ("no ethernet header!")); + ETHER_HEADER_COPY(&eh2, mtod(m2, caddr_t)); + m2 = ieee80211_mbuf_adjust(vap, 4, NULL, m2); + if (m2 == NULL) { + /* NB: ieee80211_mbuf_adjust handles msgs+statistics */ + goto bad; + } + + /* + * Now do tunnel encapsulation. First, each + * frame gets a standard encapsulation. + */ + m1 = ieee80211_ff_encap1(vap, m1, &eh1); + if (m1 == NULL) + goto bad; + m2 = ieee80211_ff_encap1(vap, m2, &eh2); + if (m2 == NULL) + goto bad; + + /* + * Pad leading frame to a 4-byte boundary. If there + * is space at the end of the first frame, put it + * there; otherwise prepend to the front of the second + * frame. We know doing the second will always work + * because we reserve space above. We prefer appending + * as this typically has better DMA alignment properties. + */ + for (m = m1; m->m_next != NULL; m = m->m_next) + ; + pad = roundup2(m1->m_pkthdr.len, 4) - m1->m_pkthdr.len; + if (pad) { + if (M_TRAILINGSPACE(m) < pad) { /* prepend to second */ + m2->m_data -= pad; + m2->m_len += pad; + m2->m_pkthdr.len += pad; + } else { /* append to first */ + m->m_len += pad; + m1->m_pkthdr.len += pad; + } + } + + /* + * Now, stick 'em together. + */ + m->m_next = m2; /* NB: last mbuf from above */ + m1->m_pkthdr.len += m2->m_pkthdr.len; + + vap->iv_stats.is_amsdu_encap++; + + return m1; +bad: + vap->iv_stats.is_amsdu_encapfail++; + if (m1 != NULL) + m_freem(m1); + if (m2 != NULL) + m_freem(m2); + return NULL; +} + + static void ff_transmit(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; int error; IEEE80211_TX_LOCK_ASSERT(vap->iv_ic); /* encap and xmit */ m = ieee80211_encap(vap, ni, m); if (m != NULL) { struct ifnet *ifp = vap->iv_ifp; error = ieee80211_parent_xmitpkt(ic, m); if (!error) if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); } else ieee80211_free_node(ni); } /* * Flush frames to device; note we re-use the linked list * the frames were stored on and use the sentinel (unchanged) * which may be non-NULL. */ static void ff_flush(struct mbuf *head, struct mbuf *last) { struct mbuf *m, *next; struct ieee80211_node *ni; struct ieee80211vap *vap; for (m = head; m != last; m = next) { next = m->m_nextpkt; m->m_nextpkt = NULL; ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; vap = ni->ni_vap; IEEE80211_NOTE(vap, IEEE80211_MSG_SUPERG, ni, "%s: flush frame, age %u", __func__, M_AGE_GET(m)); vap->iv_stats.is_ff_flush++; ff_transmit(ni, m); } } /* * Age frames on the staging queue. * * This is called without the comlock held, but it does all its work * behind the comlock. Because of this, it's possible that the * staging queue will be serviced between the function which called * it and now; thus simply checking that the queue has work in it * may fail. * * See PR kern/174283 for more details. */ void ieee80211_ff_age(struct ieee80211com *ic, struct ieee80211_stageq *sq, int quanta) { struct mbuf *m, *head; struct ieee80211_node *ni; #if 0 KASSERT(sq->head != NULL, ("stageq empty")); #endif IEEE80211_LOCK(ic); head = sq->head; while ((m = sq->head) != NULL && M_AGE_GET(m) < quanta) { int tid = WME_AC_TO_TID(M_WME_GETAC(m)); /* clear staging ref to frame */ ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; KASSERT(ni->ni_tx_superg[tid] == m, ("staging queue empty")); ni->ni_tx_superg[tid] = NULL; sq->head = m->m_nextpkt; sq->depth--; } if (m == NULL) sq->tail = NULL; else M_AGE_SUB(m, quanta); IEEE80211_UNLOCK(ic); IEEE80211_TX_LOCK(ic); ff_flush(head, m); IEEE80211_TX_UNLOCK(ic); } static void stageq_add(struct ieee80211com *ic, struct ieee80211_stageq *sq, struct mbuf *m) { int age = ieee80211_ffagemax; IEEE80211_LOCK_ASSERT(ic); if (sq->tail != NULL) { sq->tail->m_nextpkt = m; age -= M_AGE_GET(sq->head); } else sq->head = m; KASSERT(age >= 0, ("age %d", age)); M_AGE_SET(m, age); m->m_nextpkt = NULL; sq->tail = m; sq->depth++; } static void stageq_remove(struct ieee80211com *ic, struct ieee80211_stageq *sq, struct mbuf *mstaged) { struct mbuf *m, *mprev; IEEE80211_LOCK_ASSERT(ic); mprev = NULL; for (m = sq->head; m != NULL; m = m->m_nextpkt) { if (m == mstaged) { if (mprev == NULL) sq->head = m->m_nextpkt; else mprev->m_nextpkt = m->m_nextpkt; if (sq->tail == m) sq->tail = mprev; sq->depth--; return; } mprev = m; } printf("%s: packet not found\n", __func__); } static uint32_t ff_approx_txtime(struct ieee80211_node *ni, const struct mbuf *m1, const struct mbuf *m2) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; uint32_t framelen; + uint32_t frame_time; /* * Approximate the frame length to be transmitted. A swag to add * the following maximal values to the skb payload: * - 32: 802.11 encap + CRC * - 24: encryption overhead (if wep bit) * - 4 + 6: fast-frame header and padding * - 16: 2 LLC FF tunnel headers * - 14: 1 802.3 FF tunnel header (mbuf already accounts for 2nd) */ framelen = m1->m_pkthdr.len + 32 + ATH_FF_MAX_HDR_PAD + ATH_FF_MAX_SEP_PAD + ATH_FF_MAX_HDR; if (vap->iv_flags & IEEE80211_F_PRIVACY) framelen += 24; if (m2 != NULL) framelen += m2->m_pkthdr.len; - return ieee80211_compute_duration(ic->ic_rt, framelen, ni->ni_txrate, 0); + + /* + * For now, we assume non-shortgi, 20MHz, just because I want to + * at least test 802.11n. + */ + if (ni->ni_txrate & IEEE80211_RATE_MCS) + frame_time = ieee80211_compute_duration_ht(framelen, + ni->ni_txrate, + IEEE80211_HT_RC_2_STREAMS(ni->ni_txrate), + 0, /* isht40 */ + 0); /* isshortgi */ + else + frame_time = ieee80211_compute_duration(ic->ic_rt, framelen, + ni->ni_txrate, 0); + return (frame_time); } /* * Check if the supplied frame can be partnered with an existing * or pending frame. Return a reference to any frame that should be * sent on return; otherwise return NULL. */ struct mbuf * ieee80211_ff_check(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_superg *sg = ic->ic_superg; const int pri = M_WME_GETAC(m); struct ieee80211_stageq *sq; struct ieee80211_tx_ampdu *tap; struct mbuf *mstaged; uint32_t txtime, limit; IEEE80211_TX_UNLOCK_ASSERT(ic); /* * Check if the supplied frame can be aggregated. * * NB: we allow EAPOL frames to be aggregated with other ucast traffic. * Do 802.1x EAPOL frames proceed in the clear? Then they couldn't * be aggregated with other types of frames when encryption is on? */ IEEE80211_LOCK(ic); tap = &ni->ni_tx_ampdu[WME_AC_TO_TID(pri)]; mstaged = ni->ni_tx_superg[WME_AC_TO_TID(pri)]; /* XXX NOTE: reusing packet counter state from A-MPDU */ /* * XXX NOTE: this means we're double-counting; it should just * be done in ieee80211_output.c once for both superg and A-MPDU. */ ieee80211_txampdu_count_packet(tap); /* * When not in station mode never aggregate a multicast * frame; this insures, for example, that a combined frame * does not require multiple encryption keys. */ if (vap->iv_opmode != IEEE80211_M_STA && ETHER_IS_MULTICAST(mtod(m, struct ether_header *)->ether_dhost)) { /* XXX flush staged frame? */ IEEE80211_UNLOCK(ic); return m; } /* * If there is no frame to combine with and the pps is * too low; then do not attempt to aggregate this frame. */ if (mstaged == NULL && ieee80211_txampdu_getpps(tap) < ieee80211_ffppsmin) { IEEE80211_UNLOCK(ic); return m; } sq = &sg->ff_stageq[pri]; /* * Check the txop limit to insure the aggregate fits. */ limit = IEEE80211_TXOP_TO_US( ic->ic_wme.wme_chanParams.cap_wmeParams[pri].wmep_txopLimit); if (limit != 0 && (txtime = ff_approx_txtime(ni, m, mstaged)) > limit) { /* * Aggregate too long, return to the caller for direct * transmission. In addition, flush any pending frame * before sending this one. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: txtime %u exceeds txop limit %u\n", __func__, txtime, limit); ni->ni_tx_superg[WME_AC_TO_TID(pri)] = NULL; if (mstaged != NULL) stageq_remove(ic, sq, mstaged); IEEE80211_UNLOCK(ic); if (mstaged != NULL) { IEEE80211_TX_LOCK(ic); IEEE80211_NOTE(vap, IEEE80211_MSG_SUPERG, ni, "%s: flush staged frame", __func__); /* encap and xmit */ ff_transmit(ni, mstaged); IEEE80211_TX_UNLOCK(ic); } return m; /* NB: original frame */ } /* * An aggregation candidate. If there's a frame to partner * with then combine and return for processing. Otherwise * save this frame and wait for a partner to show up (or * the frame to be flushed). Note that staged frames also * hold their node reference. */ if (mstaged != NULL) { ni->ni_tx_superg[WME_AC_TO_TID(pri)] = NULL; stageq_remove(ic, sq, mstaged); IEEE80211_UNLOCK(ic); IEEE80211_NOTE(vap, IEEE80211_MSG_SUPERG, ni, "%s: aggregate fast-frame", __func__); /* * Release the node reference; we only need * the one already in mstaged. */ KASSERT(mstaged->m_pkthdr.rcvif == (void *)ni, ("rcvif %p ni %p", mstaged->m_pkthdr.rcvif, ni)); ieee80211_free_node(ni); m->m_nextpkt = NULL; mstaged->m_nextpkt = m; mstaged->m_flags |= M_FF; /* NB: mark for encap work */ } else { KASSERT(ni->ni_tx_superg[WME_AC_TO_TID(pri)]== NULL, ("ni_tx_superg[]: %p", ni->ni_tx_superg[WME_AC_TO_TID(pri)])); ni->ni_tx_superg[WME_AC_TO_TID(pri)] = m; stageq_add(ic, sq, m); IEEE80211_UNLOCK(ic); IEEE80211_NOTE(vap, IEEE80211_MSG_SUPERG, ni, "%s: stage frame, %u queued", __func__, sq->depth); /* NB: mstaged is NULL */ } return mstaged; +} + +struct mbuf * +ieee80211_amsdu_check(struct ieee80211_node *ni, struct mbuf *m) +{ + /* + * XXX TODO: actually enforce the node support + * and HTCAP requirements for the maximum A-MSDU + * size. + */ + + /* First: software A-MSDU transmit? */ + if (! ieee80211_amsdu_tx_ok(ni)) + return (m); + + /* Next - EAPOL? Nope, don't aggregate; we don't QoS encap them */ + if (m->m_flags & (M_EAPOL | M_MCAST | M_BCAST)) + return (m); + + /* Next - needs to be a data frame, non-broadcast, etc */ + if (ETHER_IS_MULTICAST(mtod(m, struct ether_header *)->ether_dhost)) + return (m); + + return (ieee80211_ff_check(ni, m)); } void ieee80211_ff_node_init(struct ieee80211_node *ni) { /* * Clean FF state on re-associate. This handles the case * where a station leaves w/o notifying us and then returns * before node is reaped for inactivity. */ ieee80211_ff_node_cleanup(ni); } void ieee80211_ff_node_cleanup(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_superg *sg = ic->ic_superg; struct mbuf *m, *next_m, *head; int tid; IEEE80211_LOCK(ic); head = NULL; for (tid = 0; tid < WME_NUM_TID; tid++) { int ac = TID_TO_WME_AC(tid); /* * XXX Initialise the packet counter. * * This may be double-work for 11n stations; * but without it we never setup things. */ ieee80211_txampdu_init_pps(&ni->ni_tx_ampdu[tid]); m = ni->ni_tx_superg[tid]; if (m != NULL) { ni->ni_tx_superg[tid] = NULL; stageq_remove(ic, &sg->ff_stageq[ac], m); m->m_nextpkt = head; head = m; } } IEEE80211_UNLOCK(ic); /* * Free mbufs, taking care to not dereference the mbuf after * we free it (hence grabbing m_nextpkt before we free it.) */ m = head; while (m != NULL) { next_m = m->m_nextpkt; m_freem(m); ieee80211_free_node(ni); m = next_m; } } /* * Switch between turbo and non-turbo operating modes. * Use the specified channel flags to locate the new * channel, update 802.11 state, and then call back into * the driver to effect the change. */ void ieee80211_dturbo_switch(struct ieee80211vap *vap, int newflags) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *chan; chan = ieee80211_find_channel(ic, ic->ic_bsschan->ic_freq, newflags); if (chan == NULL) { /* XXX should not happen */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: no channel with freq %u flags 0x%x\n", __func__, ic->ic_bsschan->ic_freq, newflags); return; } IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: %s -> %s (freq %u flags 0x%x)\n", __func__, ieee80211_phymode_name[ieee80211_chan2mode(ic->ic_bsschan)], ieee80211_phymode_name[ieee80211_chan2mode(chan)], chan->ic_freq, chan->ic_flags); ic->ic_bsschan = chan; ic->ic_prevchan = ic->ic_curchan; ic->ic_curchan = chan; ic->ic_rt = ieee80211_get_ratetable(chan); ic->ic_set_channel(ic); ieee80211_radiotap_chan_change(ic); /* NB: do not need to reset ERP state 'cuz we're in sta mode */ } /* * Return the current ``state'' of an Atheros capbility. * If associated in station mode report the negotiated * setting. Otherwise report the current setting. */ static int getathcap(struct ieee80211vap *vap, int cap) { if (vap->iv_opmode == IEEE80211_M_STA && vap->iv_state == IEEE80211_S_RUN) return IEEE80211_ATH_CAP(vap, vap->iv_bss, cap) != 0; else return (vap->iv_flags & cap) != 0; } static int superg_ioctl_get80211(struct ieee80211vap *vap, struct ieee80211req *ireq) { switch (ireq->i_type) { case IEEE80211_IOC_FF: ireq->i_val = getathcap(vap, IEEE80211_F_FF); break; case IEEE80211_IOC_TURBOP: ireq->i_val = getathcap(vap, IEEE80211_F_TURBOP); break; default: return ENOSYS; } return 0; } IEEE80211_IOCTL_GET(superg, superg_ioctl_get80211); static int superg_ioctl_set80211(struct ieee80211vap *vap, struct ieee80211req *ireq) { switch (ireq->i_type) { case IEEE80211_IOC_FF: if (ireq->i_val) { if ((vap->iv_caps & IEEE80211_C_FF) == 0) return EOPNOTSUPP; vap->iv_flags |= IEEE80211_F_FF; } else vap->iv_flags &= ~IEEE80211_F_FF; return ENETRESET; case IEEE80211_IOC_TURBOP: if (ireq->i_val) { if ((vap->iv_caps & IEEE80211_C_TURBOP) == 0) return EOPNOTSUPP; vap->iv_flags |= IEEE80211_F_TURBOP; } else vap->iv_flags &= ~IEEE80211_F_TURBOP; return ENETRESET; default: return ENOSYS; } return 0; } IEEE80211_IOCTL_SET(superg, superg_ioctl_set80211); #endif /* IEEE80211_SUPPORT_SUPERG */ Index: projects/release-pkg/sys/net80211/ieee80211_superg.h =================================================================== --- projects/release-pkg/sys/net80211/ieee80211_superg.h (revision 297604) +++ projects/release-pkg/sys/net80211/ieee80211_superg.h (revision 297605) @@ -1,136 +1,159 @@ /*- * Copyright (c) 2009 Sam Leffler, Errno Consulting * 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 _NET80211_IEEE80211_SUPERG_H_ #define _NET80211_IEEE80211_SUPERG_H_ /* * Atheros' 802.11 SuperG protocol support. */ /* * Atheros advanced capability information element. */ struct ieee80211_ath_ie { uint8_t ath_id; /* IEEE80211_ELEMID_VENDOR */ uint8_t ath_len; /* length in bytes */ uint8_t ath_oui[3]; /* ATH_OUI */ uint8_t ath_oui_type; /* ATH_OUI_TYPE */ uint8_t ath_oui_subtype; /* ATH_OUI_SUBTYPE */ uint8_t ath_version; /* spec revision */ uint8_t ath_capability; /* capability info */ #define ATHEROS_CAP_TURBO_PRIME 0x01 /* dynamic turbo--aka Turbo' */ #define ATHEROS_CAP_COMPRESSION 0x02 /* data compression */ #define ATHEROS_CAP_FAST_FRAME 0x04 /* fast (jumbo) frames */ #define ATHEROS_CAP_XR 0x08 /* Xtended Range support */ #define ATHEROS_CAP_AR 0x10 /* Advanded Radar support */ #define ATHEROS_CAP_BURST 0x20 /* Bursting - not negotiated */ #define ATHEROS_CAP_WME 0x40 /* CWMin tuning */ #define ATHEROS_CAP_BOOST 0x80 /* use turbo/!turbo mode */ uint8_t ath_defkeyix[2]; } __packed; #define ATH_OUI_VERSION 0x00 #define ATH_OUI_SUBTYPE 0x01 #ifdef _KERNEL struct ieee80211_stageq { struct mbuf *head; /* frames linked w/ m_nextpkt */ struct mbuf *tail; /* last frame in queue */ int depth; /* # items on head */ }; struct ieee80211_superg { /* fast-frames staging q */ struct ieee80211_stageq ff_stageq[WME_NUM_AC]; }; void ieee80211_superg_attach(struct ieee80211com *); void ieee80211_superg_detach(struct ieee80211com *); void ieee80211_superg_vattach(struct ieee80211vap *); void ieee80211_superg_vdetach(struct ieee80211vap *); uint8_t *ieee80211_add_ath(uint8_t *, uint8_t, ieee80211_keyix); uint8_t *ieee80211_add_athcaps(uint8_t *, const struct ieee80211_node *); void ieee80211_parse_ath(struct ieee80211_node *, uint8_t *); int ieee80211_parse_athparams(struct ieee80211_node *, uint8_t *, const struct ieee80211_frame *); void ieee80211_ff_node_init(struct ieee80211_node *); void ieee80211_ff_node_cleanup(struct ieee80211_node *); +static inline int +ieee80211_amsdu_tx_ok(struct ieee80211_node *ni) +{ + + /* First: software A-MSDU transmit? */ + if ((ni->ni_ic->ic_caps & IEEE80211_C_SWAMSDUTX) == 0) + return (0); + + /* Next: does the VAP have AMSDU TX enabled? */ + if ((ni->ni_vap->iv_flags_ht & IEEE80211_FHT_AMSDU_TX) == 0) + return (0); + + /* Next: 11n node? (assumed that A-MSDU TX to HT nodes is ok */ + if ((ni->ni_flags & IEEE80211_NODE_HT) == 0) + return (0); + + /* ok, we can at least /do/ AMSDU to this node */ + return (1); +} + +struct mbuf * ieee80211_amsdu_check(struct ieee80211_node *ni, struct mbuf *m); struct mbuf *ieee80211_ff_check(struct ieee80211_node *, struct mbuf *); void ieee80211_ff_age(struct ieee80211com *, struct ieee80211_stageq *, int quanta); /* * See ieee80211_ff_age() for a description of the locking * expectation here. */ static __inline void ieee80211_ff_flush(struct ieee80211com *ic, int ac) { struct ieee80211_superg *sg = ic->ic_superg; if (sg != NULL && sg->ff_stageq[ac].depth) ieee80211_ff_age(ic, &sg->ff_stageq[ac], 0x7fffffff); } /* * See ieee80211_ff_age() for a description of the locking * expectation here. */ static __inline void ieee80211_ff_age_all(struct ieee80211com *ic, int quanta) { struct ieee80211_superg *sg = ic->ic_superg; if (sg != NULL) { if (sg->ff_stageq[WME_AC_VO].depth) ieee80211_ff_age(ic, &sg->ff_stageq[WME_AC_VO], quanta); if (sg->ff_stageq[WME_AC_VI].depth) ieee80211_ff_age(ic, &sg->ff_stageq[WME_AC_VI], quanta); if (sg->ff_stageq[WME_AC_BE].depth) ieee80211_ff_age(ic, &sg->ff_stageq[WME_AC_BE], quanta); if (sg->ff_stageq[WME_AC_BK].depth) ieee80211_ff_age(ic, &sg->ff_stageq[WME_AC_BK], quanta); } } struct mbuf *ieee80211_ff_encap(struct ieee80211vap *, struct mbuf *, int, struct ieee80211_key *); +struct mbuf * ieee80211_amsdu_encap(struct ieee80211vap *vap, struct mbuf *m1, + int hdrspace, struct ieee80211_key *key); struct mbuf *ieee80211_ff_decap(struct ieee80211_node *, struct mbuf *); static __inline struct mbuf * ieee80211_decap_fastframe(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m) { return IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF) ? ieee80211_ff_decap(ni, m) : m; } #endif /* _KERNEL */ #endif /* _NET80211_IEEE80211_SUPERG_H_ */ Index: projects/release-pkg/sys/net80211/ieee80211_var.h =================================================================== --- projects/release-pkg/sys/net80211/ieee80211_var.h (revision 297604) +++ projects/release-pkg/sys/net80211/ieee80211_var.h (revision 297605) @@ -1,1002 +1,1004 @@ /*- * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting * 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 _NET80211_IEEE80211_VAR_H_ #define _NET80211_IEEE80211_VAR_H_ /* * Definitions for IEEE 802.11 drivers. */ /* NB: portability glue must go first */ #if defined(__NetBSD__) #include #elif defined(__FreeBSD__) #include #elif defined(__linux__) #include #else #error "No support for your operating system!" #endif #include #include #include #include #include #include /* for ieee80211_stats */ #include #include #include #include #include #include #define IEEE80211_TXPOWER_MAX 100 /* .5 dBm (XXX units?) */ #define IEEE80211_TXPOWER_MIN 0 /* kill radio */ #define IEEE80211_DTIM_DEFAULT 1 /* default DTIM period */ #define IEEE80211_BINTVAL_DEFAULT 100 /* default beacon interval (TU's) */ #define IEEE80211_BMISS_MAX 2 /* maximum consecutive bmiss allowed */ #define IEEE80211_HWBMISS_DEFAULT 7 /* h/w bmiss threshold (beacons) */ #define IEEE80211_BGSCAN_INTVAL_MIN 15 /* min bg scan intvl (secs) */ #define IEEE80211_BGSCAN_INTVAL_DEFAULT (5*60) /* default bg scan intvl */ #define IEEE80211_BGSCAN_IDLE_MIN 100 /* min idle time (ms) */ #define IEEE80211_BGSCAN_IDLE_DEFAULT 250 /* default idle time (ms) */ #define IEEE80211_SCAN_VALID_MIN 10 /* min scan valid time (secs) */ #define IEEE80211_SCAN_VALID_DEFAULT 60 /* default scan valid time */ #define IEEE80211_PS_SLEEP 0x1 /* STA is in power saving mode */ #define IEEE80211_PS_MAX_QUEUE 50 /* maximum saved packets */ #define IEEE80211_FIXED_RATE_NONE 0xff #define IEEE80211_TXMAX_DEFAULT 6 /* default ucast max retries */ #define IEEE80211_RTS_DEFAULT IEEE80211_RTS_MAX #define IEEE80211_FRAG_DEFAULT IEEE80211_FRAG_MAX #define IEEE80211_MS_TO_TU(x) (((x) * 1000) / 1024) #define IEEE80211_TU_TO_MS(x) (((x) * 1024) / 1000) /* XXX TODO: cap this at 1, in case hz is not 1000 */ #define IEEE80211_TU_TO_TICKS(x)(((uint64_t)(x) * 1024 * hz) / (1000 * 1000)) /* * 802.11 control state is split into a common portion that maps * 1-1 to a physical device and one or more "Virtual AP's" (VAP) * that are bound to an ieee80211com instance and share a single * underlying device. Each VAP has a corresponding OS device * entity through which traffic flows and that applications use * for issuing ioctls, etc. */ /* * Data common to one or more virtual AP's. State shared by * the underlying device and the net80211 layer is exposed here; * e.g. device-specific callbacks. */ struct ieee80211vap; typedef void (*ieee80211vap_attach)(struct ieee80211vap *); struct ieee80211_appie { uint16_t ie_len; /* size of ie_data */ uint8_t ie_data[]; /* user-specified IE's */ }; struct ieee80211_tdma_param; struct ieee80211_rate_table; struct ieee80211_tx_ampdu; struct ieee80211_rx_ampdu; struct ieee80211_superg; struct ieee80211_frame; struct ieee80211com { void *ic_softc; /* driver softc */ const char *ic_name; /* usually device name */ ieee80211_com_lock_t ic_comlock; /* state update lock */ ieee80211_tx_lock_t ic_txlock; /* ic/vap TX lock */ LIST_ENTRY(ieee80211com) ic_next; /* on global list */ TAILQ_HEAD(, ieee80211vap) ic_vaps; /* list of vap instances */ int ic_headroom; /* driver tx headroom needs */ enum ieee80211_phytype ic_phytype; /* XXX wrong for multi-mode */ enum ieee80211_opmode ic_opmode; /* operation mode */ struct callout ic_inact; /* inactivity processing */ struct taskqueue *ic_tq; /* deferred state thread */ struct task ic_parent_task; /* deferred parent processing */ struct task ic_promisc_task;/* deferred promisc update */ struct task ic_mcast_task; /* deferred mcast update */ struct task ic_chan_task; /* deferred channel change */ struct task ic_bmiss_task; /* deferred beacon miss hndlr */ struct task ic_chw_task; /* deferred HT CHW update */ struct task ic_wme_task; /* deferred WME update */ struct task ic_restart_task; /* deferred device restart */ counter_u64_t ic_ierrors; /* input errors */ counter_u64_t ic_oerrors; /* output errors */ uint32_t ic_flags; /* state flags */ uint32_t ic_flags_ext; /* extended state flags */ uint32_t ic_flags_ht; /* HT state flags */ uint32_t ic_flags_ven; /* vendor state flags */ uint32_t ic_caps; /* capabilities */ uint32_t ic_htcaps; /* HT capabilities */ uint32_t ic_htextcaps; /* HT extended capabilities */ uint32_t ic_cryptocaps; /* crypto capabilities */ uint8_t ic_modecaps[2]; /* set of mode capabilities */ uint8_t ic_promisc; /* vap's needing promisc mode */ uint8_t ic_allmulti; /* vap's needing all multicast*/ uint8_t ic_nrunning; /* vap's marked running */ uint8_t ic_curmode; /* current mode */ uint8_t ic_macaddr[IEEE80211_ADDR_LEN]; uint16_t ic_bintval; /* beacon interval */ uint16_t ic_lintval; /* listen interval */ uint16_t ic_holdover; /* PM hold over duration */ uint16_t ic_txpowlimit; /* global tx power limit */ struct ieee80211_rateset ic_sup_rates[IEEE80211_MODE_MAX]; /* * Channel state: * * ic_channels is the set of available channels for the device; * it is setup by the driver * ic_nchans is the number of valid entries in ic_channels * ic_chan_avail is a bit vector of these channels used to check * whether a channel is available w/o searching the channel table. * ic_chan_active is a (potentially) constrained subset of * ic_chan_avail that reflects any mode setting or user-specified * limit on the set of channels to use/scan * ic_curchan is the current channel the device is set to; it may * be different from ic_bsschan when we are off-channel scanning * or otherwise doing background work * ic_bsschan is the channel selected for operation; it may * be undefined (IEEE80211_CHAN_ANYC) * ic_prevchan is a cached ``previous channel'' used to optimize * lookups when switching back+forth between two channels * (e.g. for dynamic turbo) */ int ic_nchans; /* # entries in ic_channels */ struct ieee80211_channel ic_channels[IEEE80211_CHAN_MAX]; uint8_t ic_chan_avail[IEEE80211_CHAN_BYTES]; uint8_t ic_chan_active[IEEE80211_CHAN_BYTES]; uint8_t ic_chan_scan[IEEE80211_CHAN_BYTES]; struct ieee80211_channel *ic_curchan; /* current channel */ const struct ieee80211_rate_table *ic_rt; /* table for ic_curchan */ struct ieee80211_channel *ic_bsschan; /* bss channel */ struct ieee80211_channel *ic_prevchan; /* previous channel */ struct ieee80211_regdomain ic_regdomain;/* regulatory data */ struct ieee80211_appie *ic_countryie; /* calculated country ie */ struct ieee80211_channel *ic_countryie_chan; /* 802.11h/DFS state */ struct ieee80211_channel *ic_csa_newchan;/* channel for doing CSA */ short ic_csa_mode; /* mode for doing CSA */ short ic_csa_count; /* count for doing CSA */ struct ieee80211_dfs_state ic_dfs; /* DFS state */ struct ieee80211_scan_state *ic_scan; /* scan state */ struct ieee80211_scan_methods *ic_scan_methods; /* scan methods */ int ic_lastdata; /* time of last data frame */ int ic_lastscan; /* time last scan completed */ /* NB: this is the union of all vap stations/neighbors */ int ic_max_keyix; /* max h/w key index */ struct ieee80211_node_table ic_sta; /* stations/neighbors */ struct ieee80211_ageq ic_stageq; /* frame staging queue */ uint32_t ic_hash_key; /* random key for mac hash */ /* XXX multi-bss: split out common/vap parts */ struct ieee80211_wme_state ic_wme; /* WME/WMM state */ /* XXX multi-bss: can per-vap be done/make sense? */ enum ieee80211_protmode ic_protmode; /* 802.11g protection mode */ uint16_t ic_nonerpsta; /* # non-ERP stations */ uint16_t ic_longslotsta; /* # long slot time stations */ uint16_t ic_sta_assoc; /* stations associated */ uint16_t ic_ht_sta_assoc;/* HT stations associated */ uint16_t ic_ht40_sta_assoc;/* HT40 stations associated */ uint8_t ic_curhtprotmode;/* HTINFO bss state */ enum ieee80211_protmode ic_htprotmode; /* HT protection mode */ int ic_lastnonerp; /* last time non-ERP sta noted*/ int ic_lastnonht; /* last time non-HT sta noted */ uint8_t ic_rxstream; /* # RX streams */ uint8_t ic_txstream; /* # TX streams */ /* optional state for Atheros SuperG protocol extensions */ struct ieee80211_superg *ic_superg; /* radiotap handling */ struct ieee80211_radiotap_header *ic_th;/* tx radiotap headers */ void *ic_txchan; /* channel state in ic_th */ struct ieee80211_radiotap_header *ic_rh;/* rx radiotap headers */ void *ic_rxchan; /* channel state in ic_rh */ int ic_montaps; /* active monitor mode taps */ /* virtual ap create/delete */ struct ieee80211vap* (*ic_vap_create)(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); void (*ic_vap_delete)(struct ieee80211vap *); /* device specific ioctls */ int (*ic_ioctl)(struct ieee80211com *, u_long, void *); /* start/stop device */ void (*ic_parent)(struct ieee80211com *); /* operating mode attachment */ ieee80211vap_attach ic_vattach[IEEE80211_OPMODE_MAX]; /* return hardware/radio capabilities */ void (*ic_getradiocaps)(struct ieee80211com *, int, int *, struct ieee80211_channel []); /* check and/or prepare regdomain state change */ int (*ic_setregdomain)(struct ieee80211com *, struct ieee80211_regdomain *, int, struct ieee80211_channel []); int (*ic_set_quiet)(struct ieee80211_node *, u_int8_t *quiet_elm); /* regular transmit */ int (*ic_transmit)(struct ieee80211com *, struct mbuf *); /* send/recv 802.11 management frame */ int (*ic_send_mgmt)(struct ieee80211_node *, int, int); /* send raw 802.11 frame */ int (*ic_raw_xmit)(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); /* update device state for 802.11 slot time change */ void (*ic_updateslot)(struct ieee80211com *); /* handle multicast state changes */ void (*ic_update_mcast)(struct ieee80211com *); /* handle promiscuous mode changes */ void (*ic_update_promisc)(struct ieee80211com *); /* new station association callback/notification */ void (*ic_newassoc)(struct ieee80211_node *, int); /* TDMA update notification */ void (*ic_tdma_update)(struct ieee80211_node *, const struct ieee80211_tdma_param *, int); /* node state management */ struct ieee80211_node* (*ic_node_alloc)(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN]); void (*ic_node_free)(struct ieee80211_node *); void (*ic_node_cleanup)(struct ieee80211_node *); void (*ic_node_age)(struct ieee80211_node *); void (*ic_node_drain)(struct ieee80211_node *); int8_t (*ic_node_getrssi)(const struct ieee80211_node*); void (*ic_node_getsignal)(const struct ieee80211_node*, int8_t *, int8_t *); void (*ic_node_getmimoinfo)( const struct ieee80211_node*, struct ieee80211_mimo_info *); /* scanning support */ void (*ic_scan_start)(struct ieee80211com *); void (*ic_scan_end)(struct ieee80211com *); void (*ic_set_channel)(struct ieee80211com *); void (*ic_scan_curchan)(struct ieee80211_scan_state *, unsigned long); void (*ic_scan_mindwell)(struct ieee80211_scan_state *); /* * 802.11n ADDBA support. A simple/generic implementation * of A-MPDU tx aggregation is provided; the driver may * override these methods to provide their own support. * A-MPDU rx re-ordering happens automatically if the * driver passes out-of-order frames to ieee80211_input * from an assocated HT station. */ int (*ic_recv_action)(struct ieee80211_node *, const struct ieee80211_frame *, const uint8_t *frm, const uint8_t *efrm); int (*ic_send_action)(struct ieee80211_node *, int category, int action, void *); /* check if A-MPDU should be enabled this station+ac */ int (*ic_ampdu_enable)(struct ieee80211_node *, struct ieee80211_tx_ampdu *); /* start/stop doing A-MPDU tx aggregation for a station */ int (*ic_addba_request)(struct ieee80211_node *, struct ieee80211_tx_ampdu *, int dialogtoken, int baparamset, int batimeout); int (*ic_addba_response)(struct ieee80211_node *, struct ieee80211_tx_ampdu *, int status, int baparamset, int batimeout); void (*ic_addba_stop)(struct ieee80211_node *, struct ieee80211_tx_ampdu *); void (*ic_addba_response_timeout)(struct ieee80211_node *, struct ieee80211_tx_ampdu *); /* BAR response received */ void (*ic_bar_response)(struct ieee80211_node *, struct ieee80211_tx_ampdu *, int status); /* start/stop doing A-MPDU rx processing for a station */ int (*ic_ampdu_rx_start)(struct ieee80211_node *, struct ieee80211_rx_ampdu *, int baparamset, int batimeout, int baseqctl); void (*ic_ampdu_rx_stop)(struct ieee80211_node *, struct ieee80211_rx_ampdu *); /* The channel width has changed (20<->2040) */ void (*ic_update_chw)(struct ieee80211com *); uint64_t ic_spare[7]; }; struct ieee80211_aclator; struct ieee80211_tdma_state; struct ieee80211_mesh_state; struct ieee80211_hwmp_state; struct ieee80211vap { struct ifmedia iv_media; /* interface media config */ struct ifnet *iv_ifp; /* associated device */ struct bpf_if *iv_rawbpf; /* packet filter structure */ struct sysctl_ctx_list *iv_sysctl; /* dynamic sysctl context */ struct sysctl_oid *iv_oid; /* net.wlan.X sysctl oid */ TAILQ_ENTRY(ieee80211vap) iv_next; /* list of vap instances */ struct ieee80211com *iv_ic; /* back ptr to common state */ - const uint8_t *iv_myaddr; /* MAC address: ifp or ic */ + /* MAC address: ifp or ic */ + uint8_t iv_myaddr[IEEE80211_ADDR_LEN]; uint32_t iv_debug; /* debug msg flags */ struct ieee80211_stats iv_stats; /* statistics */ uint32_t iv_flags; /* state flags */ uint32_t iv_flags_ext; /* extended state flags */ uint32_t iv_flags_ht; /* HT state flags */ uint32_t iv_flags_ven; /* vendor state flags */ uint32_t iv_ifflags; /* ifnet flags */ uint32_t iv_caps; /* capabilities */ uint32_t iv_htcaps; /* HT capabilities */ uint32_t iv_htextcaps; /* HT extended capabilities */ enum ieee80211_opmode iv_opmode; /* operation mode */ enum ieee80211_state iv_state; /* state machine state */ enum ieee80211_state iv_nstate; /* pending state */ int iv_nstate_arg; /* pending state arg */ struct task iv_nstate_task; /* deferred state processing */ struct task iv_swbmiss_task;/* deferred iv_bmiss call */ struct callout iv_mgtsend; /* mgmt frame response timer */ /* inactivity timer settings */ int iv_inact_init; /* setting for new station */ int iv_inact_auth; /* auth but not assoc setting */ int iv_inact_run; /* authorized setting */ int iv_inact_probe; /* inactive probe time */ int iv_des_nssid; /* # desired ssids */ struct ieee80211_scan_ssid iv_des_ssid[1];/* desired ssid table */ uint8_t iv_des_bssid[IEEE80211_ADDR_LEN]; struct ieee80211_channel *iv_des_chan; /* desired channel */ uint16_t iv_des_mode; /* desired mode */ int iv_nicknamelen; /* XXX junk */ uint8_t iv_nickname[IEEE80211_NWID_LEN]; u_int iv_bgscanidle; /* bg scan idle threshold */ u_int iv_bgscanintvl; /* bg scan min interval */ u_int iv_scanvalid; /* scan cache valid threshold */ u_int iv_scanreq_duration; u_int iv_scanreq_mindwell; u_int iv_scanreq_maxdwell; uint16_t iv_scanreq_flags;/* held scan request params */ uint8_t iv_scanreq_nssid; struct ieee80211_scan_ssid iv_scanreq_ssid[IEEE80211_SCAN_MAX_SSID]; /* sta-mode roaming state */ enum ieee80211_roamingmode iv_roaming; /* roaming mode */ struct ieee80211_roamparam iv_roamparms[IEEE80211_MODE_MAX]; uint8_t iv_bmissthreshold; uint8_t iv_bmiss_count; /* current beacon miss count */ int iv_bmiss_max; /* max bmiss before scan */ uint16_t iv_swbmiss_count;/* beacons in last period */ uint16_t iv_swbmiss_period;/* s/w bmiss period */ struct callout iv_swbmiss; /* s/w beacon miss timer */ int iv_ampdu_rxmax; /* A-MPDU rx limit (bytes) */ int iv_ampdu_density;/* A-MPDU density */ int iv_ampdu_limit; /* A-MPDU tx limit (bytes) */ int iv_amsdu_limit; /* A-MSDU tx limit (bytes) */ u_int iv_ampdu_mintraffic[WME_NUM_AC]; struct ieee80211_beacon_offsets iv_bcn_off; uint32_t *iv_aid_bitmap; /* association id map */ uint16_t iv_max_aid; uint16_t iv_sta_assoc; /* stations associated */ uint16_t iv_ps_sta; /* stations in power save */ uint16_t iv_ps_pending; /* ps sta's w/ pending frames */ uint16_t iv_txseq; /* mcast xmit seq# space */ uint16_t iv_tim_len; /* ic_tim_bitmap size (bytes) */ uint8_t *iv_tim_bitmap; /* power-save stations w/ data*/ uint8_t iv_dtim_period; /* DTIM period */ uint8_t iv_dtim_count; /* DTIM count from last bcn */ /* set/unset aid pwrsav state */ uint8_t iv_quiet; /* Quiet Element */ uint8_t iv_quiet_count; /* constant count for Quiet Element */ uint8_t iv_quiet_count_value; /* variable count for Quiet Element */ uint8_t iv_quiet_period; /* period for Quiet Element */ uint16_t iv_quiet_duration; /* duration for Quiet Element */ uint16_t iv_quiet_offset; /* offset for Quiet Element */ int iv_csa_count; /* count for doing CSA */ struct ieee80211_node *iv_bss; /* information for this node */ struct ieee80211_txparam iv_txparms[IEEE80211_MODE_MAX]; uint16_t iv_rtsthreshold; uint16_t iv_fragthreshold; int iv_inact_timer; /* inactivity timer wait */ /* application-specified IE's to attach to mgt frames */ struct ieee80211_appie *iv_appie_beacon; struct ieee80211_appie *iv_appie_probereq; struct ieee80211_appie *iv_appie_proberesp; struct ieee80211_appie *iv_appie_assocreq; struct ieee80211_appie *iv_appie_assocresp; struct ieee80211_appie *iv_appie_wpa; uint8_t *iv_wpa_ie; uint8_t *iv_rsn_ie; uint16_t iv_max_keyix; /* max h/w key index */ ieee80211_keyix iv_def_txkey; /* default/group tx key index */ struct ieee80211_key iv_nw_keys[IEEE80211_WEP_NKID]; int (*iv_key_alloc)(struct ieee80211vap *, struct ieee80211_key *, ieee80211_keyix *, ieee80211_keyix *); int (*iv_key_delete)(struct ieee80211vap *, const struct ieee80211_key *); int (*iv_key_set)(struct ieee80211vap *, const struct ieee80211_key *); void (*iv_key_update_begin)(struct ieee80211vap *); void (*iv_key_update_end)(struct ieee80211vap *); const struct ieee80211_authenticator *iv_auth; /* authenticator glue */ void *iv_ec; /* private auth state */ const struct ieee80211_aclator *iv_acl; /* acl glue */ void *iv_as; /* private aclator state */ const struct ieee80211_ratectl *iv_rate; void *iv_rs; /* private ratectl state */ struct ieee80211_tdma_state *iv_tdma; /* tdma state */ struct ieee80211_mesh_state *iv_mesh; /* MBSS state */ struct ieee80211_hwmp_state *iv_hwmp; /* HWMP state */ /* operate-mode detach hook */ void (*iv_opdetach)(struct ieee80211vap *); /* receive processing */ int (*iv_input)(struct ieee80211_node *, struct mbuf *, const struct ieee80211_rx_stats *, int, int); void (*iv_recv_mgmt)(struct ieee80211_node *, struct mbuf *, int, const struct ieee80211_rx_stats *, int, int); void (*iv_recv_ctl)(struct ieee80211_node *, struct mbuf *, int); void (*iv_deliver_data)(struct ieee80211vap *, struct ieee80211_node *, struct mbuf *); #if 0 /* send processing */ int (*iv_send_mgmt)(struct ieee80211_node *, int, int); #endif /* beacon miss processing */ void (*iv_bmiss)(struct ieee80211vap *); /* reset device state after 802.11 parameter/state change */ int (*iv_reset)(struct ieee80211vap *, u_long); /* [schedule] beacon frame update */ void (*iv_update_beacon)(struct ieee80211vap *, int); /* power save handling */ void (*iv_update_ps)(struct ieee80211vap *, int); int (*iv_set_tim)(struct ieee80211_node *, int); void (*iv_node_ps)(struct ieee80211_node *, int); void (*iv_sta_ps)(struct ieee80211vap *, int); void (*iv_recv_pspoll)(struct ieee80211_node *, struct mbuf *); /* state machine processing */ int (*iv_newstate)(struct ieee80211vap *, enum ieee80211_state, int); /* 802.3 output method for raw frame xmit */ int (*iv_output)(struct ifnet *, struct mbuf *, const struct sockaddr *, struct route *); uint64_t iv_spare[6]; }; MALLOC_DECLARE(M_80211_VAP); #define IEEE80211_ADDR_EQ(a1,a2) (memcmp(a1,a2,IEEE80211_ADDR_LEN) == 0) #define IEEE80211_ADDR_COPY(dst,src) memcpy(dst,src,IEEE80211_ADDR_LEN) /* ic_flags/iv_flags */ #define IEEE80211_F_TURBOP 0x00000001 /* CONF: ATH Turbo enabled*/ #define IEEE80211_F_COMP 0x00000002 /* CONF: ATH comp enabled */ #define IEEE80211_F_FF 0x00000004 /* CONF: ATH FF enabled */ #define IEEE80211_F_BURST 0x00000008 /* CONF: bursting enabled */ /* NB: this is intentionally setup to be IEEE80211_CAPINFO_PRIVACY */ #define IEEE80211_F_PRIVACY 0x00000010 /* CONF: privacy enabled */ #define IEEE80211_F_PUREG 0x00000020 /* CONF: 11g w/o 11b sta's */ #define IEEE80211_F_SCAN 0x00000080 /* STATUS: scanning */ #define IEEE80211_F_ASCAN 0x00000100 /* STATUS: active scan */ #define IEEE80211_F_SIBSS 0x00000200 /* STATUS: start IBSS */ /* NB: this is intentionally setup to be IEEE80211_CAPINFO_SHORT_SLOTTIME */ #define IEEE80211_F_SHSLOT 0x00000400 /* STATUS: use short slot time*/ #define IEEE80211_F_PMGTON 0x00000800 /* CONF: Power mgmt enable */ #define IEEE80211_F_DESBSSID 0x00001000 /* CONF: des_bssid is set */ #define IEEE80211_F_WME 0x00002000 /* CONF: enable WME use */ #define IEEE80211_F_BGSCAN 0x00004000 /* CONF: bg scan enabled (???)*/ #define IEEE80211_F_SWRETRY 0x00008000 /* CONF: sw tx retry enabled */ #define IEEE80211_F_TXPOW_FIXED 0x00010000 /* TX Power: fixed rate */ #define IEEE80211_F_IBSSON 0x00020000 /* CONF: IBSS creation enable */ #define IEEE80211_F_SHPREAMBLE 0x00040000 /* STATUS: use short preamble */ #define IEEE80211_F_DATAPAD 0x00080000 /* CONF: do alignment pad */ #define IEEE80211_F_USEPROT 0x00100000 /* STATUS: protection enabled */ #define IEEE80211_F_USEBARKER 0x00200000 /* STATUS: use barker preamble*/ #define IEEE80211_F_CSAPENDING 0x00400000 /* STATUS: chan switch pending*/ #define IEEE80211_F_WPA1 0x00800000 /* CONF: WPA enabled */ #define IEEE80211_F_WPA2 0x01000000 /* CONF: WPA2 enabled */ #define IEEE80211_F_WPA 0x01800000 /* CONF: WPA/WPA2 enabled */ #define IEEE80211_F_DROPUNENC 0x02000000 /* CONF: drop unencrypted */ #define IEEE80211_F_COUNTERM 0x04000000 /* CONF: TKIP countermeasures */ #define IEEE80211_F_HIDESSID 0x08000000 /* CONF: hide SSID in beacon */ #define IEEE80211_F_NOBRIDGE 0x10000000 /* CONF: dis. internal bridge */ #define IEEE80211_F_PCF 0x20000000 /* CONF: PCF enabled */ #define IEEE80211_F_DOTH 0x40000000 /* CONF: 11h enabled */ #define IEEE80211_F_DWDS 0x80000000 /* CONF: Dynamic WDS enabled */ #define IEEE80211_F_BITS \ "\20\1TURBOP\2COMP\3FF\4BURST\5PRIVACY\6PUREG\10SCAN\11ASCAN\12SIBSS" \ "\13SHSLOT\14PMGTON\15DESBSSID\16WME\17BGSCAN\20SWRETRY\21TXPOW_FIXED" \ "\22IBSSON\23SHPREAMBLE\24DATAPAD\25USEPROT\26USERBARKER\27CSAPENDING" \ "\30WPA1\31WPA2\32DROPUNENC\33COUNTERM\34HIDESSID\35NOBRIDG\36PCF" \ "\37DOTH\40DWDS" /* Atheros protocol-specific flags */ #define IEEE80211_F_ATHEROS \ (IEEE80211_F_FF | IEEE80211_F_COMP | IEEE80211_F_TURBOP) /* Check if an Atheros capability was negotiated for use */ #define IEEE80211_ATH_CAP(vap, ni, bit) \ ((vap)->iv_flags & (ni)->ni_ath_flags & (bit)) /* ic_flags_ext/iv_flags_ext */ #define IEEE80211_FEXT_INACT 0x00000002 /* CONF: sta inact handling */ #define IEEE80211_FEXT_SCANWAIT 0x00000004 /* STATUS: awaiting scan */ /* 0x00000006 reserved */ #define IEEE80211_FEXT_BGSCAN 0x00000008 /* STATUS: complete bgscan */ #define IEEE80211_FEXT_WPS 0x00000010 /* CONF: WPS enabled */ #define IEEE80211_FEXT_TSN 0x00000020 /* CONF: TSN enabled */ #define IEEE80211_FEXT_SCANREQ 0x00000040 /* STATUS: scan req params */ #define IEEE80211_FEXT_RESUME 0x00000080 /* STATUS: start on resume */ #define IEEE80211_FEXT_4ADDR 0x00000100 /* CONF: apply 4-addr encap */ #define IEEE80211_FEXT_NONERP_PR 0x00000200 /* STATUS: non-ERP sta present*/ #define IEEE80211_FEXT_SWBMISS 0x00000400 /* CONF: do bmiss in s/w */ #define IEEE80211_FEXT_DFS 0x00000800 /* CONF: DFS enabled */ #define IEEE80211_FEXT_DOTD 0x00001000 /* CONF: 11d enabled */ #define IEEE80211_FEXT_STATEWAIT 0x00002000 /* STATUS: awaiting state chg */ #define IEEE80211_FEXT_REINIT 0x00004000 /* STATUS: INIT state first */ #define IEEE80211_FEXT_BPF 0x00008000 /* STATUS: BPF tap present */ /* NB: immutable: should be set only when creating a vap */ #define IEEE80211_FEXT_WDSLEGACY 0x00010000 /* CONF: legacy WDS operation */ #define IEEE80211_FEXT_PROBECHAN 0x00020000 /* CONF: probe passive channel*/ #define IEEE80211_FEXT_UNIQMAC 0x00040000 /* CONF: user or computed mac */ #define IEEE80211_FEXT_BITS \ "\20\2INACT\3SCANWAIT\4BGSCAN\5WPS\6TSN\7SCANREQ\10RESUME" \ "\0114ADDR\12NONEPR_PR\13SWBMISS\14DFS\15DOTD\16STATEWAIT\17REINIT" \ "\20BPF\21WDSLEGACY\22PROBECHAN\23UNIQMAC" /* ic_flags_ht/iv_flags_ht */ #define IEEE80211_FHT_NONHT_PR 0x00000001 /* STATUS: non-HT sta present */ #define IEEE80211_FHT_GF 0x00040000 /* CONF: Greenfield enabled */ #define IEEE80211_FHT_HT 0x00080000 /* CONF: HT supported */ #define IEEE80211_FHT_AMPDU_TX 0x00100000 /* CONF: A-MPDU tx supported */ #define IEEE80211_FHT_AMPDU_RX 0x00200000 /* CONF: A-MPDU rx supported */ #define IEEE80211_FHT_AMSDU_TX 0x00400000 /* CONF: A-MSDU tx supported */ #define IEEE80211_FHT_AMSDU_RX 0x00800000 /* CONF: A-MSDU rx supported */ #define IEEE80211_FHT_USEHT40 0x01000000 /* CONF: 20/40 use enabled */ #define IEEE80211_FHT_PUREN 0x02000000 /* CONF: 11n w/o legacy sta's */ #define IEEE80211_FHT_SHORTGI20 0x04000000 /* CONF: short GI in HT20 */ #define IEEE80211_FHT_SHORTGI40 0x08000000 /* CONF: short GI in HT40 */ #define IEEE80211_FHT_HTCOMPAT 0x10000000 /* CONF: HT vendor OUI's */ #define IEEE80211_FHT_RIFS 0x20000000 /* CONF: RIFS enabled */ #define IEEE80211_FHT_STBC_TX 0x40000000 /* CONF: STBC tx enabled */ #define IEEE80211_FHT_STBC_RX 0x80000000 /* CONF: STBC rx enabled */ #define IEEE80211_FHT_BITS \ "\20\1NONHT_PR" \ "\23GF\24HT\25AMPDU_TX\26AMPDU_TX" \ "\27AMSDU_TX\30AMSDU_RX\31USEHT40\32PUREN\33SHORTGI20\34SHORTGI40" \ "\35HTCOMPAT\36RIFS\37STBC_TX\40STBC_RX" #define IEEE80211_FVEN_BITS "\20" /* ic_caps/iv_caps: device driver capabilities */ /* 0x2e available */ #define IEEE80211_C_STA 0x00000001 /* CAPABILITY: STA available */ #define IEEE80211_C_8023ENCAP 0x00000002 /* CAPABILITY: 802.3 encap */ #define IEEE80211_C_FF 0x00000040 /* CAPABILITY: ATH FF avail */ #define IEEE80211_C_TURBOP 0x00000080 /* CAPABILITY: ATH Turbo avail*/ #define IEEE80211_C_IBSS 0x00000100 /* CAPABILITY: IBSS available */ #define IEEE80211_C_PMGT 0x00000200 /* CAPABILITY: Power mgmt */ #define IEEE80211_C_HOSTAP 0x00000400 /* CAPABILITY: HOSTAP avail */ #define IEEE80211_C_AHDEMO 0x00000800 /* CAPABILITY: Old Adhoc Demo */ #define IEEE80211_C_SWRETRY 0x00001000 /* CAPABILITY: sw tx retry */ #define IEEE80211_C_TXPMGT 0x00002000 /* CAPABILITY: tx power mgmt */ #define IEEE80211_C_SHSLOT 0x00004000 /* CAPABILITY: short slottime */ #define IEEE80211_C_SHPREAMBLE 0x00008000 /* CAPABILITY: short preamble */ #define IEEE80211_C_MONITOR 0x00010000 /* CAPABILITY: monitor mode */ #define IEEE80211_C_DFS 0x00020000 /* CAPABILITY: DFS/radar avail*/ #define IEEE80211_C_MBSS 0x00040000 /* CAPABILITY: MBSS available */ #define IEEE80211_C_SWSLEEP 0x00080000 /* CAPABILITY: do sleep here */ +#define IEEE80211_C_SWAMSDUTX 0x00100000 /* CAPABILITY: software A-MSDU TX */ /* 0x7c0000 available */ #define IEEE80211_C_WPA1 0x00800000 /* CAPABILITY: WPA1 avail */ #define IEEE80211_C_WPA2 0x01000000 /* CAPABILITY: WPA2 avail */ #define IEEE80211_C_WPA 0x01800000 /* CAPABILITY: WPA1+WPA2 avail*/ #define IEEE80211_C_BURST 0x02000000 /* CAPABILITY: frame bursting */ #define IEEE80211_C_WME 0x04000000 /* CAPABILITY: WME avail */ #define IEEE80211_C_WDS 0x08000000 /* CAPABILITY: 4-addr support */ /* 0x10000000 reserved */ #define IEEE80211_C_BGSCAN 0x20000000 /* CAPABILITY: bg scanning */ #define IEEE80211_C_TXFRAG 0x40000000 /* CAPABILITY: tx fragments */ #define IEEE80211_C_TDMA 0x80000000 /* CAPABILITY: TDMA avail */ /* XXX protection/barker? */ #define IEEE80211_C_OPMODE \ (IEEE80211_C_STA | IEEE80211_C_IBSS | IEEE80211_C_HOSTAP | \ IEEE80211_C_AHDEMO | IEEE80211_C_MONITOR | IEEE80211_C_WDS | \ IEEE80211_C_TDMA | IEEE80211_C_MBSS) #define IEEE80211_C_BITS \ "\20\1STA\002803ENCAP\7FF\10TURBOP\11IBSS\12PMGT" \ "\13HOSTAP\14AHDEMO\15SWRETRY\16TXPMGT\17SHSLOT\20SHPREAMBLE" \ "\21MONITOR\22DFS\23MBSS\30WPA1\31WPA2\32BURST\33WME\34WDS\36BGSCAN" \ "\37TXFRAG\40TDMA" /* * ic_htcaps/iv_htcaps: HT-specific device/driver capabilities * * NB: the low 16-bits are the 802.11 definitions, the upper * 16-bits are used to define s/w/driver capabilities. */ #define IEEE80211_HTC_AMPDU 0x00010000 /* CAPABILITY: A-MPDU tx */ #define IEEE80211_HTC_AMSDU 0x00020000 /* CAPABILITY: A-MSDU tx */ /* NB: HT40 is implied by IEEE80211_HTCAP_CHWIDTH40 */ #define IEEE80211_HTC_HT 0x00040000 /* CAPABILITY: HT operation */ #define IEEE80211_HTC_SMPS 0x00080000 /* CAPABILITY: MIMO power save*/ #define IEEE80211_HTC_RIFS 0x00100000 /* CAPABILITY: RIFS support */ #define IEEE80211_HTC_RXUNEQUAL 0x00200000 /* CAPABILITY: RX unequal MCS */ #define IEEE80211_HTC_RXMCS32 0x00400000 /* CAPABILITY: MCS32 support */ #define IEEE80211_HTC_TXUNEQUAL 0x00800000 /* CAPABILITY: TX unequal MCS */ #define IEEE80211_HTC_TXMCS32 0x01000000 /* CAPABILITY: MCS32 suport */ #define IEEE80211_C_HTCAP_BITS \ "\20\1LDPC\2CHWIDTH40\5GREENFIELD\6SHORTGI20\7SHORTGI40\10TXSTBC" \ "\21AMPDU\22AMSDU\23HT\24SMPS\25RIFS" int ic_printf(struct ieee80211com *, const char *, ...) __printflike(2, 3); void ieee80211_ifattach(struct ieee80211com *); void ieee80211_ifdetach(struct ieee80211com *); int ieee80211_vap_setup(struct ieee80211com *, struct ieee80211vap *, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN]); int ieee80211_vap_attach(struct ieee80211vap *, ifm_change_cb_t, ifm_stat_cb_t, const uint8_t macaddr[IEEE80211_ADDR_LEN]); void ieee80211_vap_detach(struct ieee80211vap *); const struct ieee80211_rateset *ieee80211_get_suprates(struct ieee80211com *ic, const struct ieee80211_channel *); void ieee80211_announce(struct ieee80211com *); void ieee80211_announce_channels(struct ieee80211com *); void ieee80211_drain(struct ieee80211com *); void ieee80211_chan_init(struct ieee80211com *); struct ieee80211com *ieee80211_find_vap(const uint8_t mac[IEEE80211_ADDR_LEN]); struct ieee80211com *ieee80211_find_com(const char *name); int ieee80211_media_change(struct ifnet *); void ieee80211_media_status(struct ifnet *, struct ifmediareq *); int ieee80211_ioctl(struct ifnet *, u_long, caddr_t); int ieee80211_rate2media(struct ieee80211com *, int, enum ieee80211_phymode); int ieee80211_media2rate(int); int ieee80211_mhz2ieee(u_int, u_int); int ieee80211_chan2ieee(struct ieee80211com *, const struct ieee80211_channel *); u_int ieee80211_ieee2mhz(u_int, u_int); struct ieee80211_channel *ieee80211_find_channel(struct ieee80211com *, int freq, int flags); struct ieee80211_channel *ieee80211_find_channel_byieee(struct ieee80211com *, int ieee, int flags); struct ieee80211_channel *ieee80211_lookup_channel_rxstatus(struct ieee80211vap *, const struct ieee80211_rx_stats *); int ieee80211_setmode(struct ieee80211com *, enum ieee80211_phymode); enum ieee80211_phymode ieee80211_chan2mode(const struct ieee80211_channel *); uint32_t ieee80211_mac_hash(const struct ieee80211com *, const uint8_t addr[IEEE80211_ADDR_LEN]); char ieee80211_channel_type_char(const struct ieee80211_channel *c); void ieee80211_radiotap_attach(struct ieee80211com *, struct ieee80211_radiotap_header *th, int tlen, uint32_t tx_radiotap, struct ieee80211_radiotap_header *rh, int rlen, uint32_t rx_radiotap); void ieee80211_radiotap_attachv(struct ieee80211com *, struct ieee80211_radiotap_header *th, int tlen, int n_tx_v, uint32_t tx_radiotap, struct ieee80211_radiotap_header *rh, int rlen, int n_rx_v, uint32_t rx_radiotap); void ieee80211_radiotap_detach(struct ieee80211com *); void ieee80211_radiotap_vattach(struct ieee80211vap *); void ieee80211_radiotap_vdetach(struct ieee80211vap *); void ieee80211_radiotap_chan_change(struct ieee80211com *); void ieee80211_radiotap_tx(struct ieee80211vap *, struct mbuf *); void ieee80211_radiotap_rx(struct ieee80211vap *, struct mbuf *); void ieee80211_radiotap_rx_all(struct ieee80211com *, struct mbuf *); static __inline int ieee80211_radiotap_active(const struct ieee80211com *ic) { return (ic->ic_flags_ext & IEEE80211_FEXT_BPF) != 0; } static __inline int ieee80211_radiotap_active_vap(const struct ieee80211vap *vap) { return (vap->iv_flags_ext & IEEE80211_FEXT_BPF) || vap->iv_ic->ic_montaps != 0; } /* * Enqueue a task on the state thread. */ static __inline void ieee80211_runtask(struct ieee80211com *ic, struct task *task) { taskqueue_enqueue(ic->ic_tq, task); } /* * Wait for a queued task to complete. */ static __inline void ieee80211_draintask(struct ieee80211com *ic, struct task *task) { taskqueue_drain(ic->ic_tq, task); } /* * Key update synchronization methods. XXX should not be visible. */ static __inline void ieee80211_key_update_begin(struct ieee80211vap *vap) { vap->iv_key_update_begin(vap); } static __inline void ieee80211_key_update_end(struct ieee80211vap *vap) { vap->iv_key_update_end(vap); } /* * XXX these need to be here for IEEE80211_F_DATAPAD */ /* * Return the space occupied by the 802.11 header and any * padding required by the driver. This works for a * management or data frame. */ static __inline int ieee80211_hdrspace(struct ieee80211com *ic, const void *data) { int size = ieee80211_hdrsize(data); if (ic->ic_flags & IEEE80211_F_DATAPAD) size = roundup(size, sizeof(uint32_t)); return size; } /* * Like ieee80211_hdrspace, but handles any type of frame. */ static __inline int ieee80211_anyhdrspace(struct ieee80211com *ic, const void *data) { int size = ieee80211_anyhdrsize(data); if (ic->ic_flags & IEEE80211_F_DATAPAD) size = roundup(size, sizeof(uint32_t)); return size; } /* * Notify a vap that beacon state has been updated. */ static __inline void ieee80211_beacon_notify(struct ieee80211vap *vap, int what) { if (vap->iv_state == IEEE80211_S_RUN) vap->iv_update_beacon(vap, what); } /* * Calculate HT channel promotion flags for a channel. * XXX belongs in ieee80211_ht.h but needs IEEE80211_FHT_* */ static __inline int ieee80211_htchanflags(const struct ieee80211_channel *c) { return IEEE80211_IS_CHAN_HT40(c) ? IEEE80211_FHT_HT | IEEE80211_FHT_USEHT40 : IEEE80211_IS_CHAN_HT(c) ? IEEE80211_FHT_HT : 0; } /* * Fetch the current TX power (cap) for the given node. * * This includes the node and ic/vap TX power limit as needed, * but it doesn't take into account any per-rate limit. */ static __inline uint16_t ieee80211_get_node_txpower(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; uint16_t txpower; txpower = ni->ni_txpower; txpower = MIN(txpower, ic->ic_txpowlimit); if (ic->ic_curchan != NULL) { txpower = MIN(txpower, 2 * ic->ic_curchan->ic_maxregpower); txpower = MIN(txpower, ic->ic_curchan->ic_maxpower); } return (txpower); } /* * Debugging facilities compiled in when IEEE80211_DEBUG is defined. * * The intent is that any problem in the net80211 layer can be * diagnosed by inspecting the statistics (dumped by the wlanstats * program) and/or the msgs generated by net80211. Messages are * broken into functional classes and can be controlled with the * wlandebug program. Certain of these msg groups are for facilities * that are no longer part of net80211 (e.g. IEEE80211_MSG_DOT1XSM). */ #define IEEE80211_MSG_11N 0x80000000 /* 11n mode debug */ #define IEEE80211_MSG_DEBUG 0x40000000 /* IFF_DEBUG equivalent */ #define IEEE80211_MSG_DUMPPKTS 0x20000000 /* IFF_LINK2 equivalant */ #define IEEE80211_MSG_CRYPTO 0x10000000 /* crypto work */ #define IEEE80211_MSG_INPUT 0x08000000 /* input handling */ #define IEEE80211_MSG_XRATE 0x04000000 /* rate set handling */ #define IEEE80211_MSG_ELEMID 0x02000000 /* element id parsing */ #define IEEE80211_MSG_NODE 0x01000000 /* node handling */ #define IEEE80211_MSG_ASSOC 0x00800000 /* association handling */ #define IEEE80211_MSG_AUTH 0x00400000 /* authentication handling */ #define IEEE80211_MSG_SCAN 0x00200000 /* scanning */ #define IEEE80211_MSG_OUTPUT 0x00100000 /* output handling */ #define IEEE80211_MSG_STATE 0x00080000 /* state machine */ #define IEEE80211_MSG_POWER 0x00040000 /* power save handling */ #define IEEE80211_MSG_HWMP 0x00020000 /* hybrid mesh protocol */ #define IEEE80211_MSG_DOT1XSM 0x00010000 /* 802.1x state machine */ #define IEEE80211_MSG_RADIUS 0x00008000 /* 802.1x radius client */ #define IEEE80211_MSG_RADDUMP 0x00004000 /* dump 802.1x radius packets */ #define IEEE80211_MSG_MESH 0x00002000 /* mesh networking */ #define IEEE80211_MSG_WPA 0x00001000 /* WPA/RSN protocol */ #define IEEE80211_MSG_ACL 0x00000800 /* ACL handling */ #define IEEE80211_MSG_WME 0x00000400 /* WME protocol */ #define IEEE80211_MSG_SUPERG 0x00000200 /* Atheros SuperG protocol */ #define IEEE80211_MSG_DOTH 0x00000100 /* 802.11h support */ #define IEEE80211_MSG_INACT 0x00000080 /* inactivity handling */ #define IEEE80211_MSG_ROAM 0x00000040 /* sta-mode roaming */ #define IEEE80211_MSG_RATECTL 0x00000020 /* tx rate control */ #define IEEE80211_MSG_ACTION 0x00000010 /* action frame handling */ #define IEEE80211_MSG_WDS 0x00000008 /* WDS handling */ #define IEEE80211_MSG_IOCTL 0x00000004 /* ioctl handling */ #define IEEE80211_MSG_TDMA 0x00000002 /* TDMA handling */ #define IEEE80211_MSG_ANY 0xffffffff /* anything */ #define IEEE80211_MSG_BITS \ "\20\2TDMA\3IOCTL\4WDS\5ACTION\6RATECTL\7ROAM\10INACT\11DOTH\12SUPERG" \ "\13WME\14ACL\15WPA\16RADKEYS\17RADDUMP\20RADIUS\21DOT1XSM\22HWMP" \ "\23POWER\24STATE\25OUTPUT\26SCAN\27AUTH\30ASSOC\31NODE\32ELEMID" \ "\33XRATE\34INPUT\35CRYPTO\36DUPMPKTS\37DEBUG\04011N" #ifdef IEEE80211_DEBUG #define ieee80211_msg(_vap, _m) ((_vap)->iv_debug & (_m)) #define IEEE80211_DPRINTF(_vap, _m, _fmt, ...) do { \ if (ieee80211_msg(_vap, _m)) \ ieee80211_note(_vap, _fmt, __VA_ARGS__); \ } while (0) #define IEEE80211_NOTE(_vap, _m, _ni, _fmt, ...) do { \ if (ieee80211_msg(_vap, _m)) \ ieee80211_note_mac(_vap, (_ni)->ni_macaddr, _fmt, __VA_ARGS__);\ } while (0) #define IEEE80211_NOTE_MAC(_vap, _m, _mac, _fmt, ...) do { \ if (ieee80211_msg(_vap, _m)) \ ieee80211_note_mac(_vap, _mac, _fmt, __VA_ARGS__); \ } while (0) #define IEEE80211_NOTE_FRAME(_vap, _m, _wh, _fmt, ...) do { \ if (ieee80211_msg(_vap, _m)) \ ieee80211_note_frame(_vap, _wh, _fmt, __VA_ARGS__); \ } while (0) void ieee80211_note(const struct ieee80211vap *, const char *, ...); void ieee80211_note_mac(const struct ieee80211vap *, const uint8_t mac[IEEE80211_ADDR_LEN], const char *, ...); void ieee80211_note_frame(const struct ieee80211vap *, const struct ieee80211_frame *, const char *, ...); #define ieee80211_msg_debug(_vap) \ ((_vap)->iv_debug & IEEE80211_MSG_DEBUG) #define ieee80211_msg_dumppkts(_vap) \ ((_vap)->iv_debug & IEEE80211_MSG_DUMPPKTS) #define ieee80211_msg_input(_vap) \ ((_vap)->iv_debug & IEEE80211_MSG_INPUT) #define ieee80211_msg_radius(_vap) \ ((_vap)->iv_debug & IEEE80211_MSG_RADIUS) #define ieee80211_msg_dumpradius(_vap) \ ((_vap)->iv_debug & IEEE80211_MSG_RADDUMP) #define ieee80211_msg_dumpradkeys(_vap) \ ((_vap)->iv_debug & IEEE80211_MSG_RADKEYS) #define ieee80211_msg_scan(_vap) \ ((_vap)->iv_debug & IEEE80211_MSG_SCAN) #define ieee80211_msg_assoc(_vap) \ ((_vap)->iv_debug & IEEE80211_MSG_ASSOC) /* * Emit a debug message about discarding a frame or information * element. One format is for extracting the mac address from * the frame header; the other is for when a header is not * available or otherwise appropriate. */ #define IEEE80211_DISCARD(_vap, _m, _wh, _type, _fmt, ...) do { \ if ((_vap)->iv_debug & (_m)) \ ieee80211_discard_frame(_vap, _wh, _type, _fmt, __VA_ARGS__);\ } while (0) #define IEEE80211_DISCARD_IE(_vap, _m, _wh, _type, _fmt, ...) do { \ if ((_vap)->iv_debug & (_m)) \ ieee80211_discard_ie(_vap, _wh, _type, _fmt, __VA_ARGS__);\ } while (0) #define IEEE80211_DISCARD_MAC(_vap, _m, _mac, _type, _fmt, ...) do { \ if ((_vap)->iv_debug & (_m)) \ ieee80211_discard_mac(_vap, _mac, _type, _fmt, __VA_ARGS__);\ } while (0) void ieee80211_discard_frame(const struct ieee80211vap *, const struct ieee80211_frame *, const char *type, const char *fmt, ...); void ieee80211_discard_ie(const struct ieee80211vap *, const struct ieee80211_frame *, const char *type, const char *fmt, ...); void ieee80211_discard_mac(const struct ieee80211vap *, const uint8_t mac[IEEE80211_ADDR_LEN], const char *type, const char *fmt, ...); #else #define IEEE80211_DPRINTF(_vap, _m, _fmt, ...) #define IEEE80211_NOTE(_vap, _m, _ni, _fmt, ...) #define IEEE80211_NOTE_FRAME(_vap, _m, _wh, _fmt, ...) #define IEEE80211_NOTE_MAC(_vap, _m, _mac, _fmt, ...) #define ieee80211_msg_dumppkts(_vap) 0 #define ieee80211_msg(_vap, _m) 0 #define IEEE80211_DISCARD(_vap, _m, _wh, _type, _fmt, ...) #define IEEE80211_DISCARD_IE(_vap, _m, _wh, _type, _fmt, ...) #define IEEE80211_DISCARD_MAC(_vap, _m, _mac, _type, _fmt, ...) #endif #endif /* _NET80211_IEEE80211_VAR_H_ */ Index: projects/release-pkg/sys/powerpc/mpc85xx/i2c.c =================================================================== --- projects/release-pkg/sys/powerpc/mpc85xx/i2c.c (revision 297604) +++ projects/release-pkg/sys/powerpc/mpc85xx/i2c.c (revision 297605) @@ -1,427 +1,437 @@ /*- * Copyright (C) 2008-2009 Semihalf, Michal Hajduk * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #include #include #define I2C_ADDR_REG 0x00 /* I2C slave address register */ #define I2C_FDR_REG 0x04 /* I2C frequency divider register */ #define I2C_CONTROL_REG 0x08 /* I2C control register */ #define I2C_STATUS_REG 0x0C /* I2C status register */ #define I2C_DATA_REG 0x10 /* I2C data register */ #define I2C_DFSRR_REG 0x14 /* I2C Digital Filter Sampling rate */ #define I2C_ENABLE 0x80 /* Module enable - interrupt disable */ #define I2CSR_RXAK 0x01 /* Received acknowledge */ #define I2CSR_MCF (1<<7) /* Data transfer */ #define I2CSR_MASS (1<<6) /* Addressed as a slave */ #define I2CSR_MBB (1<<5) /* Bus busy */ #define I2CSR_MAL (1<<4) /* Arbitration lost */ #define I2CSR_SRW (1<<2) /* Slave read/write */ #define I2CSR_MIF (1<<1) /* Module interrupt */ #define I2CCR_MEN (1<<7) /* Module enable */ #define I2CCR_MSTA (1<<5) /* Master/slave mode */ #define I2CCR_MTX (1<<4) /* Transmit/receive mode */ #define I2CCR_TXAK (1<<3) /* Transfer acknowledge */ #define I2CCR_RSTA (1<<2) /* Repeated START */ #define I2C_BAUD_RATE_FAST 0x31 #define I2C_BAUD_RATE_DEF 0x3F #define I2C_DFSSR_DIV 0x10 #ifdef DEBUG #define debugf(fmt, args...) do { printf("%s(): ", __func__); printf(fmt,##args); } while (0) #else #define debugf(fmt, args...) #endif struct i2c_softc { device_t dev; device_t iicbus; struct resource *res; struct mtx mutex; int rid; bus_space_handle_t bsh; bus_space_tag_t bst; }; static int i2c_probe(device_t); static int i2c_attach(device_t); static int i2c_repeated_start(device_t dev, u_char slave, int timeout); static int i2c_start(device_t dev, u_char slave, int timeout); static int i2c_stop(device_t dev); static int i2c_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr); static int i2c_read(device_t dev, char *buf, int len, int *read, int last, int delay); static int i2c_write(device_t dev, const char *buf, int len, int *sent, int timeout); +static phandle_t i2c_get_node(device_t bus, device_t dev); static device_method_t i2c_methods[] = { DEVMETHOD(device_probe, i2c_probe), DEVMETHOD(device_attach, i2c_attach), DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD(iicbus_repeated_start, i2c_repeated_start), DEVMETHOD(iicbus_start, i2c_start), DEVMETHOD(iicbus_stop, i2c_stop), DEVMETHOD(iicbus_reset, i2c_reset), DEVMETHOD(iicbus_read, i2c_read), DEVMETHOD(iicbus_write, i2c_write), DEVMETHOD(iicbus_transfer, iicbus_transfer_gen), + DEVMETHOD(ofw_bus_get_node, i2c_get_node), { 0, 0 } }; static driver_t i2c_driver = { - "i2c", + "iichb", i2c_methods, sizeof(struct i2c_softc), }; static devclass_t i2c_devclass; DRIVER_MODULE(i2c, simplebus, i2c_driver, i2c_devclass, 0, 0); DRIVER_MODULE(iicbus, i2c, iicbus_driver, iicbus_devclass, 0, 0); static __inline void i2c_write_reg(struct i2c_softc *sc, bus_size_t off, uint8_t val) { bus_space_write_1(sc->bst, sc->bsh, off, val); } static __inline uint8_t i2c_read_reg(struct i2c_softc *sc, bus_size_t off) { return (bus_space_read_1(sc->bst, sc->bsh, off)); } static __inline void i2c_flag_set(struct i2c_softc *sc, bus_size_t off, uint8_t mask) { uint8_t status; status = i2c_read_reg(sc, off); status |= mask; i2c_write_reg(sc, off, status); } static int i2c_do_wait(device_t dev, struct i2c_softc *sc, int write, int start) { int err; uint8_t status; status = i2c_read_reg(sc, I2C_STATUS_REG); if (status & I2CSR_MIF) { if (write && start && (status & I2CSR_RXAK)) { debugf("no ack %s", start ? "after sending slave address" : ""); err = IIC_ENOACK; goto error; } if (status & I2CSR_MAL) { debugf("arbitration lost"); err = IIC_EBUSERR; goto error; } if (!write && !(status & I2CSR_MCF)) { debugf("transfer unfinished"); err = IIC_EBUSERR; goto error; } } return (IIC_NOERR); error: i2c_write_reg(sc, I2C_STATUS_REG, 0x0); i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN | I2CCR_TXAK); return (err); } static int i2c_probe(device_t dev) { struct i2c_softc *sc; if (!ofw_bus_is_compatible(dev, "fsl-i2c")) return (ENXIO); sc = device_get_softc(dev); sc->rid = 0; sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->rid, RF_ACTIVE); if (sc->res == NULL) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } sc->bst = rman_get_bustag(sc->res); sc->bsh = rman_get_bushandle(sc->res); /* Enable I2C */ i2c_write_reg(sc, I2C_CONTROL_REG, I2C_ENABLE); bus_release_resource(dev, SYS_RES_MEMORY, sc->rid, sc->res); device_set_desc(dev, "I2C bus controller"); return (BUS_PROBE_DEFAULT); } static int i2c_attach(device_t dev) { struct i2c_softc *sc; sc = device_get_softc(dev); sc->dev = dev; sc->rid = 0; mtx_init(&sc->mutex, device_get_nameunit(dev), "I2C", MTX_DEF); sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->rid, RF_ACTIVE); if (sc->res == NULL) { device_printf(dev, "could not allocate resources"); mtx_destroy(&sc->mutex); return (ENXIO); } sc->bst = rman_get_bustag(sc->res); sc->bsh = rman_get_bushandle(sc->res); sc->iicbus = device_add_child(dev, "iicbus", -1); if (sc->iicbus == NULL) { device_printf(dev, "could not add iicbus child"); mtx_destroy(&sc->mutex); return (ENXIO); } bus_generic_attach(dev); return (IIC_NOERR); } static int i2c_repeated_start(device_t dev, u_char slave, int timeout) { struct i2c_softc *sc; int error; sc = device_get_softc(dev); mtx_lock(&sc->mutex); /* Set repeated start condition */ i2c_flag_set(sc, I2C_CONTROL_REG ,I2CCR_RSTA); /* Write target address - LSB is R/W bit */ i2c_write_reg(sc, I2C_DATA_REG, slave); DELAY(1250); error = i2c_do_wait(dev, sc, 1, 1); mtx_unlock(&sc->mutex); if (error) return (error); return (IIC_NOERR); } static int i2c_start(device_t dev, u_char slave, int timeout) { struct i2c_softc *sc; uint8_t status; int error; sc = device_get_softc(dev); DELAY(1000); mtx_lock(&sc->mutex); status = i2c_read_reg(sc, I2C_STATUS_REG); /* Check if bus is idle or busy */ if (status & I2CSR_MBB) { debugf("bus busy"); mtx_unlock(&sc->mutex); i2c_stop(dev); return (IIC_EBUSERR); } /* Set start condition */ i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN | I2CCR_MSTA | I2CCR_MTX); /* Write target address - LSB is R/W bit */ i2c_write_reg(sc, I2C_DATA_REG, slave); DELAY(1250); error = i2c_do_wait(dev, sc, 1, 1); mtx_unlock(&sc->mutex); if (error) return (error); return (IIC_NOERR); } static int i2c_stop(device_t dev) { struct i2c_softc *sc; sc = device_get_softc(dev); mtx_lock(&sc->mutex); i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN | I2CCR_TXAK); DELAY(1000); mtx_unlock(&sc->mutex); return (IIC_NOERR); } static int i2c_reset(device_t dev, u_char speed, u_char addr, u_char *oldadr) { struct i2c_softc *sc; uint8_t baud_rate; sc = device_get_softc(dev); switch (speed) { case IIC_FAST: baud_rate = I2C_BAUD_RATE_FAST; break; case IIC_SLOW: case IIC_UNKNOWN: case IIC_FASTEST: default: baud_rate = I2C_BAUD_RATE_DEF; break; } mtx_lock(&sc->mutex); i2c_write_reg(sc, I2C_CONTROL_REG, 0x0); i2c_write_reg(sc, I2C_STATUS_REG, 0x0); DELAY(1000); i2c_write_reg(sc, I2C_FDR_REG, baud_rate); i2c_write_reg(sc, I2C_DFSRR_REG, I2C_DFSSR_DIV); i2c_write_reg(sc, I2C_CONTROL_REG, I2C_ENABLE); DELAY(1000); mtx_unlock(&sc->mutex); return (IIC_NOERR); } static int i2c_read(device_t dev, char *buf, int len, int *read, int last, int delay) { struct i2c_softc *sc; int error; sc = device_get_softc(dev); *read = 0; mtx_lock(&sc->mutex); if (len) { if (len == 1) i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN | I2CCR_MSTA | I2CCR_TXAK); else i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN | I2CCR_MSTA); /* dummy read */ i2c_read_reg(sc, I2C_DATA_REG); DELAY(1000); } while (*read < len) { DELAY(1000); error = i2c_do_wait(dev, sc, 0, 0); if (error) { mtx_unlock(&sc->mutex); return (error); } if ((*read == len - 2) && last) { i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN | I2CCR_MSTA | I2CCR_TXAK); } if ((*read == len - 1) && last) { i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN | I2CCR_TXAK); } *buf++ = i2c_read_reg(sc, I2C_DATA_REG); (*read)++; DELAY(1250); } mtx_unlock(&sc->mutex); return (IIC_NOERR); } static int i2c_write(device_t dev, const char *buf, int len, int *sent, int timeout) { struct i2c_softc *sc; int error; sc = device_get_softc(dev); *sent = 0; mtx_lock(&sc->mutex); while (*sent < len) { i2c_write_reg(sc, I2C_DATA_REG, *buf++); DELAY(1250); error = i2c_do_wait(dev, sc, 1, 0); if (error) { mtx_unlock(&sc->mutex); return (error); } (*sent)++; } mtx_unlock(&sc->mutex); return (IIC_NOERR); +} + +static phandle_t +i2c_get_node(device_t bus, device_t dev) +{ + + /* Share controller node with iibus device. */ + return (ofw_bus_get_node(bus)); } Index: projects/release-pkg/sys/sys/param.h =================================================================== --- projects/release-pkg/sys/sys/param.h (revision 297604) +++ projects/release-pkg/sys/sys/param.h (revision 297605) @@ -1,363 +1,363 @@ /*- * Copyright (c) 1982, 1986, 1989, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * 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. * * @(#)param.h 8.3 (Berkeley) 4/4/95 * $FreeBSD$ */ #ifndef _SYS_PARAM_H_ #define _SYS_PARAM_H_ #include #define BSD 199506 /* System version (year & month). */ #define BSD4_3 1 #define BSD4_4 1 /* * __FreeBSD_version numbers are documented in the Porter's Handbook. * If you bump the version for any reason, you should update the documentation * there. * Currently this lives here in the doc/ repository: * * head/en_US.ISO8859-1/books/porters-handbook/versions/chapter.xml * * scheme is: Rxx * 'R' is in the range 0 to 4 if this is a release branch or * x.0-CURRENT before RELENG_*_0 is created, otherwise 'R' is * in the range 5 to 9. */ #undef __FreeBSD_version -#define __FreeBSD_version 1100104 /* Master, propagated to newvers */ +#define __FreeBSD_version 1100105 /* Master, propagated to newvers */ /* * __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD, * which by definition is always true on FreeBSD. This macro is also defined * on other systems that use the kernel of FreeBSD, such as GNU/kFreeBSD. * * It is tempting to use this macro in userland code when we want to enable * kernel-specific routines, and in fact it's fine to do this in code that * is part of FreeBSD itself. However, be aware that as presence of this * macro is still not widespread (e.g. older FreeBSD versions, 3rd party * compilers, etc), it is STRONGLY DISCOURAGED to check for this macro in * external applications without also checking for __FreeBSD__ as an * alternative. */ #undef __FreeBSD_kernel__ #define __FreeBSD_kernel__ #ifdef _KERNEL #define P_OSREL_SIGWAIT 700000 #define P_OSREL_SIGSEGV 700004 #define P_OSREL_MAP_ANON 800104 #define P_OSREL_MAP_FSTRICT 1100036 #define P_OSREL_SHUTDOWN_ENOTCONN 1100077 #define P_OSREL_MAJOR(x) ((x) / 100000) #endif #ifndef LOCORE #include #endif /* * Machine-independent constants (some used in following include files). * Redefined constants are from POSIX 1003.1 limits file. * * MAXCOMLEN should be >= sizeof(ac_comm) (see ) */ #include #define MAXCOMLEN 19 /* max command name remembered */ #define MAXINTERP PATH_MAX /* max interpreter file name length */ #define MAXLOGNAME 33 /* max login name length (incl. NUL) */ #define MAXUPRC CHILD_MAX /* max simultaneous processes */ #define NCARGS ARG_MAX /* max bytes for an exec function */ #define NGROUPS (NGROUPS_MAX+1) /* max number groups */ #define NOFILE OPEN_MAX /* max open files per process */ #define NOGROUP 65535 /* marker for empty group set member */ #define MAXHOSTNAMELEN 256 /* max hostname size */ #define SPECNAMELEN 63 /* max length of devicename */ /* More types and definitions used throughout the kernel. */ #ifdef _KERNEL #include #include #ifndef LOCORE #include #include #endif #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif #endif #ifndef _KERNEL /* Signals. */ #include #endif /* Machine type dependent parameters. */ #include #ifndef _KERNEL #include #endif #ifndef DEV_BSHIFT #define DEV_BSHIFT 9 /* log2(DEV_BSIZE) */ #endif #define DEV_BSIZE (1<>PAGE_SHIFT) #endif /* * btodb() is messy and perhaps slow because `bytes' may be an off_t. We * want to shift an unsigned type to avoid sign extension and we don't * want to widen `bytes' unnecessarily. Assume that the result fits in * a daddr_t. */ #ifndef btodb #define btodb(bytes) /* calculates (bytes / DEV_BSIZE) */ \ (sizeof (bytes) > sizeof(long) \ ? (daddr_t)((unsigned long long)(bytes) >> DEV_BSHIFT) \ : (daddr_t)((unsigned long)(bytes) >> DEV_BSHIFT)) #endif #ifndef dbtob #define dbtob(db) /* calculates (db * DEV_BSIZE) */ \ ((off_t)(db) << DEV_BSHIFT) #endif #define PRIMASK 0x0ff #define PCATCH 0x100 /* OR'd with pri for tsleep to check signals */ #define PDROP 0x200 /* OR'd with pri to stop re-entry of interlock mutex */ #define NZERO 0 /* default "nice" */ #define NBBY 8 /* number of bits in a byte */ #define NBPW sizeof(int) /* number of bytes per word (integer) */ #define CMASK 022 /* default file mask: S_IWGRP|S_IWOTH */ #define NODEV (dev_t)(-1) /* non-existent device */ /* * File system parameters and macros. * * MAXBSIZE - Filesystems are made out of blocks of at most MAXBSIZE bytes * per block. MAXBSIZE may be made larger without effecting * any existing filesystems as long as it does not exceed MAXPHYS, * and may be made smaller at the risk of not being able to use * filesystems which require a block size exceeding MAXBSIZE. * * MAXBCACHEBUF - Maximum size of a buffer in the buffer cache. This must * be >= MAXBSIZE and can be set differently for different * architectures by defining it in . * Making this larger allows NFS to do larger reads/writes. * * BKVASIZE - Nominal buffer space per buffer, in bytes. BKVASIZE is the * minimum KVM memory reservation the kernel is willing to make. * Filesystems can of course request smaller chunks. Actual * backing memory uses a chunk size of a page (PAGE_SIZE). * The default value here can be overridden on a per-architecture * basis by defining it in . This should * probably be done to increase its value, when MAXBCACHEBUF is * defined as a larger value in . * * If you make BKVASIZE too small you risk seriously fragmenting * the buffer KVM map which may slow things down a bit. If you * make it too big the kernel will not be able to optimally use * the KVM memory reserved for the buffer cache and will wind * up with too-few buffers. * * The default is 16384, roughly 2x the block size used by a * normal UFS filesystem. */ #define MAXBSIZE 65536 /* must be power of 2 */ #ifndef MAXBCACHEBUF #define MAXBCACHEBUF MAXBSIZE /* must be a power of 2 >= MAXBSIZE */ #endif #ifndef BKVASIZE #define BKVASIZE 16384 /* must be power of 2 */ #endif #define BKVAMASK (BKVASIZE-1) /* * MAXPATHLEN defines the longest permissible path length after expanding * symbolic links. It is used to allocate a temporary buffer from the buffer * pool in which to do the name expansion, hence should be a power of two, * and must be less than or equal to MAXBSIZE. MAXSYMLINKS defines the * maximum number of symbolic links that may be expanded in a path name. * It should be set high enough to allow all legitimate uses, but halt * infinite loops reasonably quickly. */ #define MAXPATHLEN PATH_MAX #define MAXSYMLINKS 32 /* Bit map related macros. */ #define setbit(a,i) (((unsigned char *)(a))[(i)/NBBY] |= 1<<((i)%NBBY)) #define clrbit(a,i) (((unsigned char *)(a))[(i)/NBBY] &= ~(1<<((i)%NBBY))) #define isset(a,i) \ (((const unsigned char *)(a))[(i)/NBBY] & (1<<((i)%NBBY))) #define isclr(a,i) \ ((((const unsigned char *)(a))[(i)/NBBY] & (1<<((i)%NBBY))) == 0) /* Macros for counting and rounding. */ #ifndef howmany #define howmany(x, y) (((x)+((y)-1))/(y)) #endif #define nitems(x) (sizeof((x)) / sizeof((x)[0])) #define rounddown(x, y) (((x)/(y))*(y)) #define rounddown2(x, y) ((x)&(~((y)-1))) /* if y is power of two */ #define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) /* to any y */ #define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */ #define powerof2(x) ((((x)-1)&(x))==0) /* Macros for min/max. */ #define MIN(a,b) (((a)<(b))?(a):(b)) #define MAX(a,b) (((a)>(b))?(a):(b)) #ifdef _KERNEL /* * Basic byte order function prototypes for non-inline functions. */ #ifndef LOCORE #ifndef _BYTEORDER_PROTOTYPED #define _BYTEORDER_PROTOTYPED __BEGIN_DECLS __uint32_t htonl(__uint32_t); __uint16_t htons(__uint16_t); __uint32_t ntohl(__uint32_t); __uint16_t ntohs(__uint16_t); __END_DECLS #endif #endif #ifndef lint #ifndef _BYTEORDER_FUNC_DEFINED #define _BYTEORDER_FUNC_DEFINED #define htonl(x) __htonl(x) #define htons(x) __htons(x) #define ntohl(x) __ntohl(x) #define ntohs(x) __ntohs(x) #endif /* !_BYTEORDER_FUNC_DEFINED */ #endif /* lint */ #endif /* _KERNEL */ /* * Scale factor for scaled integers used to count %cpu time and load avgs. * * The number of CPU `tick's that map to a unique `%age' can be expressed * by the formula (1 / (2 ^ (FSHIFT - 11))). The maximum load average that * can be calculated (assuming 32 bits) can be closely approximated using * the formula (2 ^ (2 * (16 - FSHIFT))) for (FSHIFT < 15). * * For the scheduler to maintain a 1:1 mapping of CPU `tick' to `%age', * FSHIFT must be at least 11; this gives us a maximum load avg of ~1024. */ #define FSHIFT 11 /* bits to right of fixed binary point */ #define FSCALE (1<> (PAGE_SHIFT - DEV_BSHIFT)) #define ctodb(db) /* calculates pages to devblks */ \ ((db) << (PAGE_SHIFT - DEV_BSHIFT)) /* * Old spelling of __containerof(). */ #define member2struct(s, m, x) \ ((struct s *)(void *)((char *)(x) - offsetof(struct s, m))) /* * Access a variable length array that has been declared as a fixed * length array. */ #define __PAST_END(array, offset) (((__typeof__(*(array)) *)(array))[offset]) #endif /* _SYS_PARAM_H_ */ Index: projects/release-pkg/sys/x86/x86/mp_x86.c =================================================================== --- projects/release-pkg/sys/x86/x86/mp_x86.c (revision 297604) +++ projects/release-pkg/sys/x86/x86/mp_x86.c (revision 297605) @@ -1,1456 +1,1525 @@ /*- * Copyright (c) 1996, by Steve Passe * Copyright (c) 2003, by Peter Wemm * 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. The name of the developer 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$"); #ifdef __i386__ #include "opt_apic.h" #endif #include "opt_cpu.h" #include "opt_kstack_pages.h" #include "opt_pmap.h" #include "opt_sched.h" #include "opt_smp.h" #include #include #include #include /* cngetc() */ #include #ifdef GPROF #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define WARMBOOT_TARGET 0 #define WARMBOOT_OFF (KERNBASE + 0x0467) #define WARMBOOT_SEG (KERNBASE + 0x0469) #define CMOS_REG (0x70) #define CMOS_DATA (0x71) #define BIOS_RESET (0x0f) #define BIOS_WARM (0x0a) /* lock region used by kernel profiling */ int mcount_lock; int mp_naps; /* # of Applications processors */ int boot_cpu_id = -1; /* designated BSP */ extern struct pcpu __pcpu[]; /* AP uses this during bootstrap. Do not staticize. */ char *bootSTK; int bootAP; /* Free these after use */ void *bootstacks[MAXCPU]; void *dpcpu; struct pcb stoppcbs[MAXCPU]; struct susppcb **susppcbs; #ifdef COUNT_IPIS /* Interrupt counts. */ static u_long *ipi_preempt_counts[MAXCPU]; static u_long *ipi_ast_counts[MAXCPU]; u_long *ipi_invltlb_counts[MAXCPU]; u_long *ipi_invlrng_counts[MAXCPU]; u_long *ipi_invlpg_counts[MAXCPU]; u_long *ipi_invlcache_counts[MAXCPU]; u_long *ipi_rendezvous_counts[MAXCPU]; static u_long *ipi_hardclock_counts[MAXCPU]; #endif /* Default cpu_ops implementation. */ struct cpu_ops cpu_ops; /* * Local data and functions. */ static volatile cpuset_t ipi_stop_nmi_pending; /* used to hold the AP's until we are ready to release them */ struct mtx ap_boot_mtx; /* Set to 1 once we're ready to let the APs out of the pen. */ volatile int aps_ready = 0; /* * Store data from cpu_add() until later in the boot when we actually setup * the APs. */ struct cpu_info cpu_info[MAX_APIC_ID + 1]; int apic_cpuids[MAX_APIC_ID + 1]; int cpu_apic_ids[MAXCPU]; /* Holds pending bitmap based IPIs per CPU */ volatile u_int cpu_ipi_pending[MAXCPU]; static void release_aps(void *dummy); static int hyperthreading_allowed = 1; SYSCTL_INT(_machdep, OID_AUTO, hyperthreading_allowed, CTLFLAG_RDTUN, &hyperthreading_allowed, 0, "Use Intel HTT logical CPUs"); static struct topo_node topo_root; static int pkg_id_shift; static int core_id_shift; static int disabled_cpus; struct cache_info { int id_shift; int present; } static caches[MAX_CACHE_LEVELS]; void mem_range_AP_init(void) { if (mem_range_softc.mr_op && mem_range_softc.mr_op->initAP) mem_range_softc.mr_op->initAP(&mem_range_softc); } /* * Round up to the next power of two, if necessary, and then * take log2. * Returns -1 if argument is zero. */ static __inline int mask_width(u_int x) { return (fls(x << (1 - powerof2(x))) - 1); } +/* + * Add a cache level to the cache topology description. + */ static int add_deterministic_cache(int type, int level, int share_count) { if (type == 0) return (0); if (type > 3) { printf("unexpected cache type %d\n", type); return (1); } if (type == 2) /* ignore instruction cache */ return (1); if (level == 0 || level > MAX_CACHE_LEVELS) { printf("unexpected cache level %d\n", type); return (1); } if (caches[level - 1].present) { printf("WARNING: multiple entries for L%u data cache\n", level); printf("%u => %u\n", caches[level - 1].id_shift, mask_width(share_count)); } caches[level - 1].id_shift = mask_width(share_count); caches[level - 1].present = 1; if (caches[level - 1].id_shift > pkg_id_shift) { printf("WARNING: L%u data cache covers more " "APIC IDs than a package\n", level); printf("%u > %u\n", caches[level - 1].id_shift, pkg_id_shift); caches[level - 1].id_shift = pkg_id_shift; } if (caches[level - 1].id_shift < core_id_shift) { printf("WARNING: L%u data cache covers less " "APIC IDs than a core\n", level); printf("%u < %u\n", caches[level - 1].id_shift, core_id_shift); caches[level - 1].id_shift = core_id_shift; } return (1); } +/* + * Determine topology of processing units and caches for AMD CPUs. + * See: + * - AMD CPUID Specification (Publication # 25481) + * - BKDG For AMD Family 10h Processors (Publication # 31116), section 2.15 + * - BKDG for AMD NPT Family 0Fh Processors (Publication # 32559) + * XXX At the moment the code does not recognize grouping of AMD CMT threads, + * if supported, into cores, so each thread is treated as being in its own + * core. In other words, each logical CPU is considered to be a core. + */ static void topo_probe_amd(void) { u_int p[4]; int level; int share_count; int type; int i; /* No multi-core capability. */ if ((amd_feature2 & AMDID2_CMP) == 0) return; /* For families 10h and newer. */ pkg_id_shift = (cpu_procinfo2 & AMDID_COREID_SIZE) >> AMDID_COREID_SIZE_SHIFT; /* For 0Fh family. */ if (pkg_id_shift == 0) pkg_id_shift = mask_width((cpu_procinfo2 & AMDID_CMP_CORES) + 1); if ((amd_feature2 & AMDID2_TOPOLOGY) != 0) { for (i = 0; ; i++) { cpuid_count(0x8000001d, i, p); type = p[0] & 0x1f; level = (p[0] >> 5) & 0x7; share_count = 1 + ((p[0] >> 14) & 0xfff); if (!add_deterministic_cache(type, level, share_count)) break; } } else { if (cpu_exthigh >= 0x80000005) { cpuid_count(0x80000005, 0, p); if (((p[2] >> 24) & 0xff) != 0) { caches[0].id_shift = 0; caches[0].present = 1; } } if (cpu_exthigh >= 0x80000006) { cpuid_count(0x80000006, 0, p); if (((p[2] >> 16) & 0xffff) != 0) { caches[1].id_shift = 0; caches[1].present = 1; } if (((p[3] >> 18) & 0x3fff) != 0) { /* * TODO: Account for dual-node processors * where each node within a package has its own * L3 cache. */ caches[2].id_shift = pkg_id_shift; caches[2].present = 1; } } } } +/* + * Determine topology of processing units for Intel CPUs + * using CPUID Leaf 1 and Leaf 4, if supported. + * See: + * - Intel 64 Architecture Processor Topology Enumeration + * - Intel 64 and IA-32 ArchitecturesSoftware Developer’s Manual, + * Volume 3A: System Programming Guide, PROGRAMMING CONSIDERATIONS + * FOR HARDWARE MULTI-THREADING CAPABLE PROCESSORS + */ static void topo_probe_intel_0x4(void) { u_int p[4]; int max_cores; int max_logical; /* Both zero and one here mean one logical processor per package. */ max_logical = (cpu_feature & CPUID_HTT) != 0 ? (cpu_procinfo & CPUID_HTT_CORES) >> 16 : 1; if (max_logical <= 1) return; if (cpu_high >= 0x4) { cpuid_count(0x04, 0, p); max_cores = ((p[0] >> 26) & 0x3f) + 1; } else max_cores = 1; core_id_shift = mask_width(max_logical/max_cores); KASSERT(core_id_shift >= 0, ("intel topo: max_cores > max_logical\n")); pkg_id_shift = core_id_shift + mask_width(max_cores); } +/* + * Determine topology of processing units for Intel CPUs + * using CPUID Leaf 11, if supported. + * See: + * - Intel 64 Architecture Processor Topology Enumeration + * - Intel 64 and IA-32 ArchitecturesSoftware Developer’s Manual, + * Volume 3A: System Programming Guide, PROGRAMMING CONSIDERATIONS + * FOR HARDWARE MULTI-THREADING CAPABLE PROCESSORS + */ static void topo_probe_intel_0xb(void) { u_int p[4]; int bits; int type; int i; /* Fall back if CPU leaf 11 doesn't really exist. */ cpuid_count(0x0b, 0, p); if (p[1] == 0) { topo_probe_intel_0x4(); return; } /* We only support three levels for now. */ for (i = 0; ; i++) { cpuid_count(0x0b, i, p); bits = p[0] & 0x1f; type = (p[2] >> 8) & 0xff; if (type == 0) break; /* TODO: check for duplicate (re-)assignment */ if (type == CPUID_TYPE_SMT) core_id_shift = bits; else if (type == CPUID_TYPE_CORE) pkg_id_shift = bits; else printf("unknown CPU level type %d\n", type); } if (pkg_id_shift < core_id_shift) { printf("WARNING: core covers more APIC IDs than a package\n"); core_id_shift = pkg_id_shift; } } +/* + * Determine topology of caches for Intel CPUs. + * See: + * - Intel 64 Architecture Processor Topology Enumeration + * - Intel 64 and IA-32 Architectures Software Developer’s Manual + * Volume 2A: Instruction Set Reference, A-M, + * CPUID instruction + */ static void topo_probe_intel_caches(void) { u_int p[4]; int level; int share_count; int type; int i; if (cpu_high < 0x4) { /* * Available cache level and sizes can be determined * via CPUID leaf 2, but that requires a huge table of hardcoded * values, so for now just assume L1 and L2 caches potentially * shared only by HTT processing units, if HTT is present. */ caches[0].id_shift = pkg_id_shift; caches[0].present = 1; caches[1].id_shift = pkg_id_shift; caches[1].present = 1; return; } for (i = 0; ; i++) { cpuid_count(0x4, i, p); type = p[0] & 0x1f; level = (p[0] >> 5) & 0x7; share_count = 1 + ((p[0] >> 14) & 0xfff); if (!add_deterministic_cache(type, level, share_count)) break; } } +/* + * Determine topology of processing units and caches for Intel CPUs. + * See: + * - Intel 64 Architecture Processor Topology Enumeration + */ static void topo_probe_intel(void) { /* - * See Intel(R) 64 Architecture Processor - * Topology Enumeration article for details. - * * Note that 0x1 <= cpu_high < 4 case should be * compatible with topo_probe_intel_0x4() logic when * CPUID.1:EBX[23:16] > 0 (cpu_cores will be 1) * or it should trigger the fallback otherwise. */ if (cpu_high >= 0xb) topo_probe_intel_0xb(); else if (cpu_high >= 0x1) topo_probe_intel_0x4(); topo_probe_intel_caches(); } /* * Topology information is queried only on BSP, on which this * code runs and for which it can query CPUID information. * Then topology is extrapolated on all packages using an * assumption that APIC ID to hardware component ID mapping is * homogenious. * That doesn't necesserily imply that the topology is uniform. */ void topo_probe(void) { static int cpu_topo_probed = 0; struct x86_topo_layer { int type; int subtype; int id_shift; } topo_layers[MAX_CACHE_LEVELS + 3]; struct topo_node *parent; struct topo_node *node; int layer; int nlayers; int node_id; int i; if (cpu_topo_probed) return; CPU_ZERO(&logical_cpus_mask); if (mp_ncpus <= 1) ; /* nothing */ else if (cpu_vendor_id == CPU_VENDOR_AMD) topo_probe_amd(); else if (cpu_vendor_id == CPU_VENDOR_INTEL) topo_probe_intel(); KASSERT(pkg_id_shift >= core_id_shift, ("bug in APIC topology discovery")); nlayers = 0; bzero(topo_layers, sizeof(topo_layers)); topo_layers[nlayers].type = TOPO_TYPE_PKG; topo_layers[nlayers].id_shift = pkg_id_shift; if (bootverbose) printf("Package ID shift: %u\n", topo_layers[nlayers].id_shift); nlayers++; /* * Consider all caches to be within a package/chip * and "in front" of all sub-components like * cores and hardware threads. */ for (i = MAX_CACHE_LEVELS - 1; i >= 0; --i) { if (caches[i].present) { KASSERT(caches[i].id_shift <= pkg_id_shift, ("bug in APIC topology discovery")); KASSERT(caches[i].id_shift >= core_id_shift, ("bug in APIC topology discovery")); topo_layers[nlayers].type = TOPO_TYPE_CACHE; topo_layers[nlayers].subtype = i + 1; topo_layers[nlayers].id_shift = caches[i].id_shift; if (bootverbose) printf("L%u cache ID shift: %u\n", topo_layers[nlayers].subtype, topo_layers[nlayers].id_shift); nlayers++; } } if (pkg_id_shift > core_id_shift) { topo_layers[nlayers].type = TOPO_TYPE_CORE; topo_layers[nlayers].id_shift = core_id_shift; if (bootverbose) printf("Core ID shift: %u\n", topo_layers[nlayers].id_shift); nlayers++; } topo_layers[nlayers].type = TOPO_TYPE_PU; topo_layers[nlayers].id_shift = 0; nlayers++; topo_init_root(&topo_root); for (i = 0; i <= MAX_APIC_ID; ++i) { if (!cpu_info[i].cpu_present) continue; parent = &topo_root; for (layer = 0; layer < nlayers; ++layer) { node_id = i >> topo_layers[layer].id_shift; parent = topo_add_node_by_hwid(parent, node_id, topo_layers[layer].type, topo_layers[layer].subtype); } } parent = &topo_root; for (layer = 0; layer < nlayers; ++layer) { node_id = boot_cpu_id >> topo_layers[layer].id_shift; node = topo_find_node_by_hwid(parent, node_id, topo_layers[layer].type, topo_layers[layer].subtype); topo_promote_child(node); parent = node; } cpu_topo_probed = 1; } /* * Assign logical CPU IDs to local APICs. */ void assign_cpu_ids(void) { struct topo_node *node; u_int smt_mask; smt_mask = (1u << core_id_shift) - 1; /* * Assign CPU IDs to local APIC IDs and disable any CPUs * beyond MAXCPU. CPU 0 is always assigned to the BSP. */ mp_ncpus = 0; TOPO_FOREACH(node, &topo_root) { if (node->type != TOPO_TYPE_PU) continue; if ((node->hwid & smt_mask) != (boot_cpu_id & smt_mask)) cpu_info[node->hwid].cpu_hyperthread = 1; if (resource_disabled("lapic", node->hwid)) { if (node->hwid != boot_cpu_id) cpu_info[node->hwid].cpu_disabled = 1; else printf("Cannot disable BSP, APIC ID = %d\n", node->hwid); } if (!hyperthreading_allowed && cpu_info[node->hwid].cpu_hyperthread) cpu_info[node->hwid].cpu_disabled = 1; if (mp_ncpus >= MAXCPU) cpu_info[node->hwid].cpu_disabled = 1; if (cpu_info[node->hwid].cpu_disabled) { disabled_cpus++; continue; } cpu_apic_ids[mp_ncpus] = node->hwid; apic_cpuids[node->hwid] = mp_ncpus; topo_set_pu_id(node, mp_ncpus); mp_ncpus++; } KASSERT(mp_maxid >= mp_ncpus - 1, ("%s: counters out of sync: max %d, count %d", __func__, mp_maxid, mp_ncpus)); } /* * Print various information about the SMP system hardware and setup. */ void cpu_mp_announce(void) { struct topo_node *node; const char *hyperthread; int pkg_count; int cores_per_pkg; int thrs_per_core; printf("FreeBSD/SMP: "); if (topo_analyze(&topo_root, 1, &pkg_count, &cores_per_pkg, &thrs_per_core)) { printf("%d package(s)", pkg_count); if (cores_per_pkg > 0) printf(" x %d core(s)", cores_per_pkg); if (thrs_per_core > 1) printf(" x %d hardware threads", thrs_per_core); } else { printf("Non-uniform topology"); } printf("\n"); if (disabled_cpus) { printf("FreeBSD/SMP Online: "); if (topo_analyze(&topo_root, 0, &pkg_count, &cores_per_pkg, &thrs_per_core)) { printf("%d package(s)", pkg_count); if (cores_per_pkg > 0) printf(" x %d core(s)", cores_per_pkg); if (thrs_per_core > 1) printf(" x %d hardware threads", thrs_per_core); } else { printf("Non-uniform topology"); } printf("\n"); } if (!bootverbose) return; TOPO_FOREACH(node, &topo_root) { switch (node->type) { case TOPO_TYPE_PKG: printf("Package HW ID = %u (%#x)\n", node->hwid, node->hwid); break; case TOPO_TYPE_CORE: printf("\tCore HW ID = %u (%#x)\n", node->hwid, node->hwid); break; case TOPO_TYPE_PU: if (cpu_info[node->hwid].cpu_hyperthread) hyperthread = "/HT"; else hyperthread = ""; if (node->subtype == 0) printf("\t\tCPU (AP%s): APIC ID: %u (%#x)" "(disabled)\n", hyperthread, node->hwid, node->hwid); else if (node->id == 0) printf("\t\tCPU0 (BSP): APIC ID: %u (%#x)\n", node->hwid, node->hwid); else printf("\t\tCPU%u (AP%s): APIC ID: %u (%#x)\n", node->id, hyperthread, node->hwid, node->hwid); break; default: /* ignored */ break; } } } +/* + * Add a scheduling group, a group of logical processors sharing + * a particular cache (and, thus having an affinity), to the scheduling + * topology. + * This function recursively works on lower level caches. + */ static void x86topo_add_sched_group(struct topo_node *root, struct cpu_group *cg_root) { struct topo_node *node; int nchildren; int ncores; int i; KASSERT(root->type == TOPO_TYPE_SYSTEM || root->type == TOPO_TYPE_CACHE, ("x86topo_add_sched_group: bad type: %u", root->type)); CPU_COPY(&root->cpuset, &cg_root->cg_mask); cg_root->cg_count = root->cpu_count; if (root->type == TOPO_TYPE_SYSTEM) cg_root->cg_level = CG_SHARE_NONE; else cg_root->cg_level = root->subtype; + /* + * Check how many core nodes we have under the given root node. + * If we have multiple logical processors, but not multiple + * cores, then those processors must be hardware threads. + */ ncores = 0; node = root; while (node != NULL) { if (node->type != TOPO_TYPE_CORE) { node = topo_next_node(root, node); continue; } ncores++; node = topo_next_nonchild_node(root, node); } if (cg_root->cg_level != CG_SHARE_NONE && root->cpu_count > 1 && ncores < 2) cg_root->cg_flags = CG_FLAG_SMT; + /* + * Find out how many cache nodes we have under the given root node. + * We ignore cache nodes that cover all the same processors as the + * root node. Also, we do not descend below found cache nodes. + * That is, we count top-level "non-redundant" caches under the root + * node. + */ nchildren = 0; node = root; while (node != NULL) { if (node->type != TOPO_TYPE_CACHE || (root->type != TOPO_TYPE_SYSTEM && CPU_CMP(&node->cpuset, &root->cpuset) == 0)) { node = topo_next_node(root, node); continue; } nchildren++; node = topo_next_nonchild_node(root, node); } cg_root->cg_child = smp_topo_alloc(nchildren); cg_root->cg_children = nchildren; + /* + * Now find again the same cache nodes as above and recursively + * build scheduling topologies for them. + */ node = root; i = 0; while (node != NULL) { if (node->type != TOPO_TYPE_CACHE || (root->type != TOPO_TYPE_SYSTEM && CPU_CMP(&node->cpuset, &root->cpuset) == 0)) { node = topo_next_node(root, node); continue; } cg_root->cg_child[i].cg_parent = cg_root; x86topo_add_sched_group(node, &cg_root->cg_child[i]); i++; node = topo_next_nonchild_node(root, node); } } +/* + * Build the MI scheduling topology from the discovered hardware topology. + */ struct cpu_group * cpu_topo(void) { struct cpu_group *cg_root; if (mp_ncpus <= 1) return (smp_topo_none()); cg_root = smp_topo_alloc(1); x86topo_add_sched_group(&topo_root, cg_root); return (cg_root); } +/* + * Add a logical CPU to the topology. + */ void cpu_add(u_int apic_id, char boot_cpu) { if (apic_id > MAX_APIC_ID) { panic("SMP: APIC ID %d too high", apic_id); return; } KASSERT(cpu_info[apic_id].cpu_present == 0, ("CPU %d added twice", apic_id)); cpu_info[apic_id].cpu_present = 1; if (boot_cpu) { KASSERT(boot_cpu_id == -1, ("CPU %d claims to be BSP, but CPU %d already is", apic_id, boot_cpu_id)); boot_cpu_id = apic_id; cpu_info[apic_id].cpu_bsp = 1; } if (mp_ncpus < MAXCPU) { mp_ncpus++; mp_maxid = mp_ncpus - 1; } if (bootverbose) printf("SMP: Added CPU %d (%s)\n", apic_id, boot_cpu ? "BSP" : "AP"); } void cpu_mp_setmaxid(void) { /* * mp_ncpus and mp_maxid should be already set by calls to cpu_add(). * If there were no calls to cpu_add() assume this is a UP system. */ if (mp_ncpus == 0) mp_ncpus = 1; } int cpu_mp_probe(void) { /* * Always record BSP in CPU map so that the mbuf init code works * correctly. */ CPU_SETOF(0, &all_cpus); return (mp_ncpus > 1); } /* * AP CPU's call this to initialize themselves. */ void init_secondary_tail(void) { u_int cpuid; /* * On real hardware, switch to x2apic mode if possible. Do it * after aps_ready was signalled, to avoid manipulating the * mode while BSP might still want to send some IPI to us * (second startup IPI is ignored on modern hardware etc). */ lapic_xapic_mode(); /* Initialize the PAT MSR. */ pmap_init_pat(); /* set up CPU registers and state */ cpu_setregs(); /* set up SSE/NX */ initializecpu(); /* set up FPU state on the AP */ #ifdef __amd64__ fpuinit(); #else npxinit(false); #endif if (cpu_ops.cpu_init) cpu_ops.cpu_init(); /* A quick check from sanity claus */ cpuid = PCPU_GET(cpuid); if (PCPU_GET(apic_id) != lapic_id()) { printf("SMP: cpuid = %d\n", cpuid); printf("SMP: actual apic_id = %d\n", lapic_id()); printf("SMP: correct apic_id = %d\n", PCPU_GET(apic_id)); panic("cpuid mismatch! boom!!"); } /* Initialize curthread. */ KASSERT(PCPU_GET(idlethread) != NULL, ("no idle thread")); PCPU_SET(curthread, PCPU_GET(idlethread)); mca_init(); mtx_lock_spin(&ap_boot_mtx); /* Init local apic for irq's */ lapic_setup(1); /* Set memory range attributes for this CPU to match the BSP */ mem_range_AP_init(); smp_cpus++; CTR1(KTR_SMP, "SMP: AP CPU #%d Launched", cpuid); printf("SMP: AP CPU #%d Launched!\n", cpuid); /* Determine if we are a logical CPU. */ if (cpu_info[PCPU_GET(apic_id)].cpu_hyperthread) CPU_SET(cpuid, &logical_cpus_mask); if (bootverbose) lapic_dump("AP"); if (smp_cpus == mp_ncpus) { /* enable IPI's, tlb shootdown, freezes etc */ atomic_store_rel_int(&smp_started, 1); } #ifdef __amd64__ /* * Enable global pages TLB extension * This also implicitly flushes the TLB */ load_cr4(rcr4() | CR4_PGE); if (pmap_pcid_enabled) load_cr4(rcr4() | CR4_PCIDE); load_ds(_udatasel); load_es(_udatasel); load_fs(_ufssel); #endif mtx_unlock_spin(&ap_boot_mtx); /* Wait until all the AP's are up. */ while (atomic_load_acq_int(&smp_started) == 0) ia32_pause(); /* Start per-CPU event timers. */ cpu_initclocks_ap(); sched_throw(NULL); panic("scheduler returned us to %s", __func__); /* NOTREACHED */ } /******************************************************************* * local functions and data */ /* * We tell the I/O APIC code about all the CPUs we want to receive * interrupts. If we don't want certain CPUs to receive IRQs we * can simply not tell the I/O APIC code about them in this function. * We also do not tell it about the BSP since it tells itself about * the BSP internally to work with UP kernels and on UP machines. */ void set_interrupt_apic_ids(void) { u_int i, apic_id; for (i = 0; i < MAXCPU; i++) { apic_id = cpu_apic_ids[i]; if (apic_id == -1) continue; if (cpu_info[apic_id].cpu_bsp) continue; if (cpu_info[apic_id].cpu_disabled) continue; /* Don't let hyperthreads service interrupts. */ if (cpu_info[apic_id].cpu_hyperthread) continue; intr_add_cpu(i); } } #ifdef COUNT_XINVLTLB_HITS u_int xhits_gbl[MAXCPU]; u_int xhits_pg[MAXCPU]; u_int xhits_rng[MAXCPU]; static SYSCTL_NODE(_debug, OID_AUTO, xhits, CTLFLAG_RW, 0, ""); SYSCTL_OPAQUE(_debug_xhits, OID_AUTO, global, CTLFLAG_RW, &xhits_gbl, sizeof(xhits_gbl), "IU", ""); SYSCTL_OPAQUE(_debug_xhits, OID_AUTO, page, CTLFLAG_RW, &xhits_pg, sizeof(xhits_pg), "IU", ""); SYSCTL_OPAQUE(_debug_xhits, OID_AUTO, range, CTLFLAG_RW, &xhits_rng, sizeof(xhits_rng), "IU", ""); u_int ipi_global; u_int ipi_page; u_int ipi_range; u_int ipi_range_size; SYSCTL_INT(_debug_xhits, OID_AUTO, ipi_global, CTLFLAG_RW, &ipi_global, 0, ""); SYSCTL_INT(_debug_xhits, OID_AUTO, ipi_page, CTLFLAG_RW, &ipi_page, 0, ""); SYSCTL_INT(_debug_xhits, OID_AUTO, ipi_range, CTLFLAG_RW, &ipi_range, 0, ""); SYSCTL_INT(_debug_xhits, OID_AUTO, ipi_range_size, CTLFLAG_RW, &ipi_range_size, 0, ""); #endif /* COUNT_XINVLTLB_HITS */ /* * Init and startup IPI. */ void ipi_startup(int apic_id, int vector) { /* * This attempts to follow the algorithm described in the * Intel Multiprocessor Specification v1.4 in section B.4. * For each IPI, we allow the local APIC ~20us to deliver the * IPI. If that times out, we panic. */ /* * first we do an INIT IPI: this INIT IPI might be run, resetting * and running the target CPU. OR this INIT IPI might be latched (P5 * bug), CPU waiting for STARTUP IPI. OR this INIT IPI might be * ignored. */ lapic_ipi_raw(APIC_DEST_DESTFLD | APIC_TRIGMOD_LEVEL | APIC_LEVEL_ASSERT | APIC_DESTMODE_PHY | APIC_DELMODE_INIT, apic_id); lapic_ipi_wait(100); /* Explicitly deassert the INIT IPI. */ lapic_ipi_raw(APIC_DEST_DESTFLD | APIC_TRIGMOD_LEVEL | APIC_LEVEL_DEASSERT | APIC_DESTMODE_PHY | APIC_DELMODE_INIT, apic_id); DELAY(10000); /* wait ~10mS */ /* * next we do a STARTUP IPI: the previous INIT IPI might still be * latched, (P5 bug) this 1st STARTUP would then terminate * immediately, and the previously started INIT IPI would continue. OR * the previous INIT IPI has already run. and this STARTUP IPI will * run. OR the previous INIT IPI was ignored. and this STARTUP IPI * will run. */ lapic_ipi_raw(APIC_DEST_DESTFLD | APIC_TRIGMOD_EDGE | APIC_LEVEL_ASSERT | APIC_DESTMODE_PHY | APIC_DELMODE_STARTUP | vector, apic_id); if (!lapic_ipi_wait(100)) panic("Failed to deliver first STARTUP IPI to APIC %d", apic_id); DELAY(200); /* wait ~200uS */ /* * finally we do a 2nd STARTUP IPI: this 2nd STARTUP IPI should run IF * the previous STARTUP IPI was cancelled by a latched INIT IPI. OR * this STARTUP IPI will be ignored, as only ONE STARTUP IPI is * recognized after hardware RESET or INIT IPI. */ lapic_ipi_raw(APIC_DEST_DESTFLD | APIC_TRIGMOD_EDGE | APIC_LEVEL_ASSERT | APIC_DESTMODE_PHY | APIC_DELMODE_STARTUP | vector, apic_id); if (!lapic_ipi_wait(100)) panic("Failed to deliver second STARTUP IPI to APIC %d", apic_id); DELAY(200); /* wait ~200uS */ } /* * Send an IPI to specified CPU handling the bitmap logic. */ void ipi_send_cpu(int cpu, u_int ipi) { u_int bitmap, old_pending, new_pending; KASSERT(cpu_apic_ids[cpu] != -1, ("IPI to non-existent CPU %d", cpu)); if (IPI_IS_BITMAPED(ipi)) { bitmap = 1 << ipi; ipi = IPI_BITMAP_VECTOR; do { old_pending = cpu_ipi_pending[cpu]; new_pending = old_pending | bitmap; } while (!atomic_cmpset_int(&cpu_ipi_pending[cpu], old_pending, new_pending)); if (old_pending) return; } lapic_ipi_vectored(ipi, cpu_apic_ids[cpu]); } void ipi_bitmap_handler(struct trapframe frame) { struct trapframe *oldframe; struct thread *td; int cpu = PCPU_GET(cpuid); u_int ipi_bitmap; critical_enter(); td = curthread; td->td_intr_nesting_level++; oldframe = td->td_intr_frame; td->td_intr_frame = &frame; ipi_bitmap = atomic_readandclear_int(&cpu_ipi_pending[cpu]); if (ipi_bitmap & (1 << IPI_PREEMPT)) { #ifdef COUNT_IPIS (*ipi_preempt_counts[cpu])++; #endif sched_preempt(td); } if (ipi_bitmap & (1 << IPI_AST)) { #ifdef COUNT_IPIS (*ipi_ast_counts[cpu])++; #endif /* Nothing to do for AST */ } if (ipi_bitmap & (1 << IPI_HARDCLOCK)) { #ifdef COUNT_IPIS (*ipi_hardclock_counts[cpu])++; #endif hardclockintr(); } td->td_intr_frame = oldframe; td->td_intr_nesting_level--; critical_exit(); } /* * send an IPI to a set of cpus. */ void ipi_selected(cpuset_t cpus, u_int ipi) { int cpu; /* * IPI_STOP_HARD maps to a NMI and the trap handler needs a bit * of help in order to understand what is the source. * Set the mask of receiving CPUs for this purpose. */ if (ipi == IPI_STOP_HARD) CPU_OR_ATOMIC(&ipi_stop_nmi_pending, &cpus); while ((cpu = CPU_FFS(&cpus)) != 0) { cpu--; CPU_CLR(cpu, &cpus); CTR3(KTR_SMP, "%s: cpu: %d ipi: %x", __func__, cpu, ipi); ipi_send_cpu(cpu, ipi); } } /* * send an IPI to a specific CPU. */ void ipi_cpu(int cpu, u_int ipi) { /* * IPI_STOP_HARD maps to a NMI and the trap handler needs a bit * of help in order to understand what is the source. * Set the mask of receiving CPUs for this purpose. */ if (ipi == IPI_STOP_HARD) CPU_SET_ATOMIC(cpu, &ipi_stop_nmi_pending); CTR3(KTR_SMP, "%s: cpu: %d ipi: %x", __func__, cpu, ipi); ipi_send_cpu(cpu, ipi); } /* * send an IPI to all CPUs EXCEPT myself */ void ipi_all_but_self(u_int ipi) { cpuset_t other_cpus; other_cpus = all_cpus; CPU_CLR(PCPU_GET(cpuid), &other_cpus); if (IPI_IS_BITMAPED(ipi)) { ipi_selected(other_cpus, ipi); return; } /* * IPI_STOP_HARD maps to a NMI and the trap handler needs a bit * of help in order to understand what is the source. * Set the mask of receiving CPUs for this purpose. */ if (ipi == IPI_STOP_HARD) CPU_OR_ATOMIC(&ipi_stop_nmi_pending, &other_cpus); CTR2(KTR_SMP, "%s: ipi: %x", __func__, ipi); lapic_ipi_vectored(ipi, APIC_IPI_DEST_OTHERS); } int ipi_nmi_handler(void) { u_int cpuid; /* * As long as there is not a simple way to know about a NMI's * source, if the bitmask for the current CPU is present in * the global pending bitword an IPI_STOP_HARD has been issued * and should be handled. */ cpuid = PCPU_GET(cpuid); if (!CPU_ISSET(cpuid, &ipi_stop_nmi_pending)) return (1); CPU_CLR_ATOMIC(cpuid, &ipi_stop_nmi_pending); cpustop_handler(); return (0); } /* * Handle an IPI_STOP by saving our current context and spinning until we * are resumed. */ void cpustop_handler(void) { u_int cpu; cpu = PCPU_GET(cpuid); savectx(&stoppcbs[cpu]); /* Indicate that we are stopped */ CPU_SET_ATOMIC(cpu, &stopped_cpus); /* Wait for restart */ while (!CPU_ISSET(cpu, &started_cpus)) ia32_pause(); CPU_CLR_ATOMIC(cpu, &started_cpus); CPU_CLR_ATOMIC(cpu, &stopped_cpus); #if defined(__amd64__) && defined(DDB) amd64_db_resume_dbreg(); #endif if (cpu == 0 && cpustop_restartfunc != NULL) { cpustop_restartfunc(); cpustop_restartfunc = NULL; } } /* * Handle an IPI_SUSPEND by saving our current context and spinning until we * are resumed. */ void cpususpend_handler(void) { u_int cpu; mtx_assert(&smp_ipi_mtx, MA_NOTOWNED); cpu = PCPU_GET(cpuid); if (savectx(&susppcbs[cpu]->sp_pcb)) { #ifdef __amd64__ fpususpend(susppcbs[cpu]->sp_fpususpend); #else npxsuspend(susppcbs[cpu]->sp_fpususpend); #endif wbinvd(); CPU_SET_ATOMIC(cpu, &suspended_cpus); } else { #ifdef __amd64__ fpuresume(susppcbs[cpu]->sp_fpususpend); #else npxresume(susppcbs[cpu]->sp_fpususpend); #endif pmap_init_pat(); initializecpu(); PCPU_SET(switchtime, 0); PCPU_SET(switchticks, ticks); /* Indicate that we are resumed */ CPU_CLR_ATOMIC(cpu, &suspended_cpus); } /* Wait for resume */ while (!CPU_ISSET(cpu, &started_cpus)) ia32_pause(); if (cpu_ops.cpu_resume) cpu_ops.cpu_resume(); #ifdef __amd64__ if (vmm_resume_p) vmm_resume_p(); #endif /* Resume MCA and local APIC */ lapic_xapic_mode(); mca_resume(); lapic_setup(0); /* Indicate that we are resumed */ CPU_CLR_ATOMIC(cpu, &suspended_cpus); CPU_CLR_ATOMIC(cpu, &started_cpus); } void invlcache_handler(void) { #ifdef COUNT_IPIS (*ipi_invlcache_counts[PCPU_GET(cpuid)])++; #endif /* COUNT_IPIS */ wbinvd(); atomic_add_int(&smp_tlb_wait, 1); } /* * This is called once the rest of the system is up and running and we're * ready to let the AP's out of the pen. */ static void release_aps(void *dummy __unused) { if (mp_ncpus == 1) return; atomic_store_rel_int(&aps_ready, 1); while (smp_started == 0) ia32_pause(); } SYSINIT(start_aps, SI_SUB_SMP, SI_ORDER_FIRST, release_aps, NULL); #ifdef COUNT_IPIS /* * Setup interrupt counters for IPI handlers. */ static void mp_ipi_intrcnt(void *dummy) { char buf[64]; int i; CPU_FOREACH(i) { snprintf(buf, sizeof(buf), "cpu%d:invltlb", i); intrcnt_add(buf, &ipi_invltlb_counts[i]); snprintf(buf, sizeof(buf), "cpu%d:invlrng", i); intrcnt_add(buf, &ipi_invlrng_counts[i]); snprintf(buf, sizeof(buf), "cpu%d:invlpg", i); intrcnt_add(buf, &ipi_invlpg_counts[i]); snprintf(buf, sizeof(buf), "cpu%d:invlcache", i); intrcnt_add(buf, &ipi_invlcache_counts[i]); snprintf(buf, sizeof(buf), "cpu%d:preempt", i); intrcnt_add(buf, &ipi_preempt_counts[i]); snprintf(buf, sizeof(buf), "cpu%d:ast", i); intrcnt_add(buf, &ipi_ast_counts[i]); snprintf(buf, sizeof(buf), "cpu%d:rendezvous", i); intrcnt_add(buf, &ipi_rendezvous_counts[i]); snprintf(buf, sizeof(buf), "cpu%d:hardclock", i); intrcnt_add(buf, &ipi_hardclock_counts[i]); } } SYSINIT(mp_ipi_intrcnt, SI_SUB_INTR, SI_ORDER_MIDDLE, mp_ipi_intrcnt, NULL); #endif /* * Flush the TLB on other CPU's */ /* Variables needed for SMP tlb shootdown. */ static vm_offset_t smp_tlb_addr1, smp_tlb_addr2; pmap_t smp_tlb_pmap; volatile int smp_tlb_wait; #ifdef __amd64__ #define read_eflags() read_rflags() #endif static void smp_targeted_tlb_shootdown(cpuset_t mask, u_int vector, pmap_t pmap, vm_offset_t addr1, vm_offset_t addr2) { int cpu, ncpu, othercpus; othercpus = mp_ncpus - 1; /* does not shootdown self */ /* * Check for other cpus. Return if none. */ if (CPU_ISFULLSET(&mask)) { if (othercpus < 1) return; } else { CPU_CLR(PCPU_GET(cpuid), &mask); if (CPU_EMPTY(&mask)) return; } if (!(read_eflags() & PSL_I)) panic("%s: interrupts disabled", __func__); mtx_lock_spin(&smp_ipi_mtx); smp_tlb_addr1 = addr1; smp_tlb_addr2 = addr2; smp_tlb_pmap = pmap; smp_tlb_wait = 0; if (CPU_ISFULLSET(&mask)) { ncpu = othercpus; ipi_all_but_self(vector); } else { ncpu = 0; while ((cpu = CPU_FFS(&mask)) != 0) { cpu--; CPU_CLR(cpu, &mask); CTR3(KTR_SMP, "%s: cpu: %d ipi: %x", __func__, cpu, vector); ipi_send_cpu(cpu, vector); ncpu++; } } while (smp_tlb_wait < ncpu) ia32_pause(); mtx_unlock_spin(&smp_ipi_mtx); } void smp_masked_invltlb(cpuset_t mask, pmap_t pmap) { if (smp_started) { smp_targeted_tlb_shootdown(mask, IPI_INVLTLB, pmap, 0, 0); #ifdef COUNT_XINVLTLB_HITS ipi_global++; #endif } } void smp_masked_invlpg(cpuset_t mask, vm_offset_t addr) { if (smp_started) { smp_targeted_tlb_shootdown(mask, IPI_INVLPG, NULL, addr, 0); #ifdef COUNT_XINVLTLB_HITS ipi_page++; #endif } } void smp_masked_invlpg_range(cpuset_t mask, vm_offset_t addr1, vm_offset_t addr2) { if (smp_started) { smp_targeted_tlb_shootdown(mask, IPI_INVLRNG, NULL, addr1, addr2); #ifdef COUNT_XINVLTLB_HITS ipi_range++; ipi_range_size += (addr2 - addr1) / PAGE_SIZE; #endif } } void smp_cache_flush(void) { if (smp_started) { smp_targeted_tlb_shootdown(all_cpus, IPI_INVLCACHE, NULL, 0, 0); } } /* * Handlers for TLB related IPIs */ void invltlb_handler(void) { #ifdef COUNT_XINVLTLB_HITS xhits_gbl[PCPU_GET(cpuid)]++; #endif /* COUNT_XINVLTLB_HITS */ #ifdef COUNT_IPIS (*ipi_invltlb_counts[PCPU_GET(cpuid)])++; #endif /* COUNT_IPIS */ if (smp_tlb_pmap == kernel_pmap) invltlb_glob(); else invltlb(); atomic_add_int(&smp_tlb_wait, 1); } void invlpg_handler(void) { #ifdef COUNT_XINVLTLB_HITS xhits_pg[PCPU_GET(cpuid)]++; #endif /* COUNT_XINVLTLB_HITS */ #ifdef COUNT_IPIS (*ipi_invlpg_counts[PCPU_GET(cpuid)])++; #endif /* COUNT_IPIS */ invlpg(smp_tlb_addr1); atomic_add_int(&smp_tlb_wait, 1); } void invlrng_handler(void) { vm_offset_t addr; #ifdef COUNT_XINVLTLB_HITS xhits_rng[PCPU_GET(cpuid)]++; #endif /* COUNT_XINVLTLB_HITS */ #ifdef COUNT_IPIS (*ipi_invlrng_counts[PCPU_GET(cpuid)])++; #endif /* COUNT_IPIS */ addr = smp_tlb_addr1; do { invlpg(addr); addr += PAGE_SIZE; } while (addr < smp_tlb_addr2); atomic_add_int(&smp_tlb_wait, 1); } Index: projects/release-pkg/sys =================================================================== --- projects/release-pkg/sys (revision 297604) +++ projects/release-pkg/sys (revision 297605) Property changes on: projects/release-pkg/sys ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/sys:r297567-297604 Index: projects/release-pkg/usr.bin/sed/compile.c =================================================================== --- projects/release-pkg/usr.bin/sed/compile.c (revision 297604) +++ projects/release-pkg/usr.bin/sed/compile.c (revision 297605) @@ -1,950 +1,949 @@ /*- * Copyright (c) 1992 Diomidis Spinellis. * Copyright (c) 1992, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Diomidis Spinellis of Imperial College, University of London. * * 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. */ #include __FBSDID("$FreeBSD$"); #ifndef lint static const char sccsid[] = "@(#)compile.c 8.1 (Berkeley) 6/6/93"; #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "defs.h" #include "extern.h" #define LHSZ 128 #define LHMASK (LHSZ - 1) static struct labhash { struct labhash *lh_next; u_int lh_hash; struct s_command *lh_cmd; int lh_ref; } *labels[LHSZ]; static char *compile_addr(char *, struct s_addr *); static char *compile_ccl(char **, char *); static char *compile_delimited(char *, char *, int); static char *compile_flags(char *, struct s_subst *); static regex_t *compile_re(char *, int); static char *compile_subst(char *, struct s_subst *); static char *compile_text(void); static char *compile_tr(char *, struct s_tr **); static struct s_command **compile_stream(struct s_command **); static char *duptoeol(char *, const char *); static void enterlabel(struct s_command *); static struct s_command *findlabel(char *); static void fixuplabel(struct s_command *, struct s_command *); static void uselabel(void); /* * Command specification. This is used to drive the command parser. */ struct s_format { char code; /* Command code */ int naddr; /* Number of address args */ enum e_args args; /* Argument type */ }; static struct s_format cmd_fmts[] = { {'{', 2, GROUP}, {'}', 0, ENDGROUP}, {'a', 1, TEXT}, {'b', 2, BRANCH}, {'c', 2, TEXT}, {'d', 2, EMPTY}, {'D', 2, EMPTY}, {'g', 2, EMPTY}, {'G', 2, EMPTY}, {'h', 2, EMPTY}, {'H', 2, EMPTY}, {'i', 1, TEXT}, {'l', 2, EMPTY}, {'n', 2, EMPTY}, {'N', 2, EMPTY}, {'p', 2, EMPTY}, {'P', 2, EMPTY}, {'q', 1, EMPTY}, {'r', 1, RFILE}, {'s', 2, SUBST}, {'t', 2, BRANCH}, {'w', 2, WFILE}, {'x', 2, EMPTY}, {'y', 2, TR}, {'!', 2, NONSEL}, {':', 0, LABEL}, {'#', 0, COMMENT}, {'=', 1, EMPTY}, {'\0', 0, COMMENT}, }; /* The compiled program. */ struct s_command *prog; /* * Compile the program into prog. * Initialise appends. */ void compile(void) { *compile_stream(&prog) = NULL; fixuplabel(prog, NULL); uselabel(); if (appendnum == 0) appends = NULL; else if ((appends = malloc(sizeof(struct s_appends) * appendnum)) == NULL) err(1, "malloc"); if ((match = malloc((maxnsub + 1) * sizeof(regmatch_t))) == NULL) err(1, "malloc"); } #define EATSPACE() do { \ if (p) \ while (*p && isspace((unsigned char)*p)) \ p++; \ } while (0) static struct s_command ** compile_stream(struct s_command **link) { char *p; static char lbuf[_POSIX2_LINE_MAX + 1]; /* To save stack */ struct s_command *cmd, *cmd2, *stack; struct s_format *fp; char re[_POSIX2_LINE_MAX + 1]; int naddr; /* Number of addresses */ stack = 0; for (;;) { if ((p = cu_fgets(lbuf, sizeof(lbuf), NULL)) == NULL) { if (stack != 0) errx(1, "%lu: %s: unexpected EOF (pending }'s)", linenum, fname); return (link); } semicolon: EATSPACE(); if (p) { if (*p == '#' || *p == '\0') continue; else if (*p == ';') { p++; goto semicolon; } } if ((*link = cmd = malloc(sizeof(struct s_command))) == NULL) err(1, "malloc"); link = &cmd->next; cmd->startline = cmd->nonsel = 0; /* First parse the addresses */ naddr = 0; /* Valid characters to start an address */ #define addrchar(c) (strchr("0123456789/\\$", (c))) if (addrchar(*p)) { naddr++; if ((cmd->a1 = malloc(sizeof(struct s_addr))) == NULL) err(1, "malloc"); p = compile_addr(p, cmd->a1); EATSPACE(); /* EXTENSION */ if (*p == ',') { p++; EATSPACE(); /* EXTENSION */ naddr++; if ((cmd->a2 = malloc(sizeof(struct s_addr))) == NULL) err(1, "malloc"); p = compile_addr(p, cmd->a2); EATSPACE(); } else cmd->a2 = 0; } else cmd->a1 = cmd->a2 = 0; nonsel: /* Now parse the command */ if (!*p) errx(1, "%lu: %s: command expected", linenum, fname); cmd->code = *p; for (fp = cmd_fmts; fp->code; fp++) if (fp->code == *p) break; if (!fp->code) errx(1, "%lu: %s: invalid command code %c", linenum, fname, *p); if (naddr > fp->naddr) errx(1, "%lu: %s: command %c expects up to %d address(es), found %d", linenum, fname, *p, fp->naddr, naddr); switch (fp->args) { case NONSEL: /* ! */ p++; EATSPACE(); cmd->nonsel = 1; goto nonsel; case GROUP: /* { */ p++; EATSPACE(); cmd->next = stack; stack = cmd; link = &cmd->u.c; if (*p) goto semicolon; break; case ENDGROUP: /* * Short-circuit command processing, since end of * group is really just a noop. */ cmd->nonsel = 1; if (stack == 0) errx(1, "%lu: %s: unexpected }", linenum, fname); cmd2 = stack; stack = cmd2->next; cmd2->next = cmd; /*FALLTHROUGH*/ case EMPTY: /* d D g G h H l n N p P q x = \0 */ p++; EATSPACE(); if (*p == ';') { p++; link = &cmd->next; goto semicolon; } if (*p) errx(1, "%lu: %s: extra characters at the end of %c command", linenum, fname, cmd->code); break; case TEXT: /* a c i */ p++; EATSPACE(); if (*p != '\\') errx(1, "%lu: %s: command %c expects \\ followed by text", linenum, fname, cmd->code); p++; EATSPACE(); if (*p) errx(1, "%lu: %s: extra characters after \\ at the end of %c command", linenum, fname, cmd->code); cmd->t = compile_text(); break; case COMMENT: /* \0 # */ break; case WFILE: /* w */ p++; EATSPACE(); if (*p == '\0') errx(1, "%lu: %s: filename expected", linenum, fname); cmd->t = duptoeol(p, "w command"); if (aflag) cmd->u.fd = -1; else if ((cmd->u.fd = open(p, O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, DEFFILEMODE)) == -1) err(1, "%s", p); break; case RFILE: /* r */ p++; EATSPACE(); if (*p == '\0') errx(1, "%lu: %s: filename expected", linenum, fname); else cmd->t = duptoeol(p, "read command"); break; case BRANCH: /* b t */ p++; EATSPACE(); if (*p == '\0') cmd->t = NULL; else cmd->t = duptoeol(p, "branch"); break; case LABEL: /* : */ p++; EATSPACE(); cmd->t = duptoeol(p, "label"); if (strlen(p) == 0) errx(1, "%lu: %s: empty label", linenum, fname); enterlabel(cmd); break; case SUBST: /* s */ p++; if (*p == '\0' || *p == '\\') errx(1, "%lu: %s: substitute pattern can not be delimited by newline or backslash", linenum, fname); if ((cmd->u.s = calloc(1, sizeof(struct s_subst))) == NULL) err(1, "malloc"); p = compile_delimited(p, re, 0); if (p == NULL) errx(1, "%lu: %s: unterminated substitute pattern", linenum, fname); /* Compile RE with no case sensitivity temporarily */ if (*re == '\0') cmd->u.s->re = NULL; else cmd->u.s->re = compile_re(re, 0); --p; p = compile_subst(p, cmd->u.s); p = compile_flags(p, cmd->u.s); /* Recompile RE with case sensitivity from "I" flag if any */ if (*re == '\0') cmd->u.s->re = NULL; else cmd->u.s->re = compile_re(re, cmd->u.s->icase); EATSPACE(); if (*p == ';') { p++; link = &cmd->next; goto semicolon; } break; case TR: /* y */ p++; p = compile_tr(p, &cmd->u.y); EATSPACE(); if (*p == ';') { p++; link = &cmd->next; goto semicolon; } if (*p) errx(1, "%lu: %s: extra text at the end of a transform command", linenum, fname); break; } } } /* * Get a delimited string. P points to the delimiter of the string; d points * to a buffer area. Newline and delimiter escapes are processed; other * escapes are ignored. * * Returns a pointer to the first character after the final delimiter or NULL * in the case of a non-terminated string. The character array d is filled * with the processed string. */ static char * compile_delimited(char *p, char *d, int is_tr) { char c; c = *p++; if (c == '\0') return (NULL); else if (c == '\\') errx(1, "%lu: %s: \\ can not be used as a string delimiter", linenum, fname); else if (c == '\n') errx(1, "%lu: %s: newline can not be used as a string delimiter", linenum, fname); while (*p) { if (*p == '[' && *p != c) { if ((d = compile_ccl(&p, d)) == NULL) errx(1, "%lu: %s: unbalanced brackets ([])", linenum, fname); continue; } else if (*p == '\\' && p[1] == '[') { *d++ = *p++; } else if (*p == '\\' && p[1] == c) p++; else if (*p == '\\' && p[1] == 'n') { *d++ = '\n'; p += 2; continue; } else if (*p == '\\' && p[1] == '\\') { if (is_tr) p++; else *d++ = *p++; } else if (*p == c) { *d = '\0'; return (p + 1); } *d++ = *p++; } return (NULL); } /* compile_ccl: expand a POSIX character class */ static char * compile_ccl(char **sp, char *t) { int c, d; char *s = *sp; *t++ = *s++; if (*s == '^') *t++ = *s++; if (*s == ']') *t++ = *s++; for (; *s && (*t = *s) != ']'; s++, t++) if (*s == '[' && ((d = *(s+1)) == '.' || d == ':' || d == '=')) { *++t = *++s, t++, s++; for (c = *s; (*t = *s) != ']' || c != d; s++, t++) if ((c = *s) == '\0') return NULL; } return (*s == ']') ? *sp = ++s, ++t : NULL; } /* * Compiles the regular expression in RE and returns a pointer to the compiled * regular expression. * Cflags are passed to regcomp. */ static regex_t * compile_re(char *re, int case_insensitive) { regex_t *rep; int eval, flags; flags = rflags; if (case_insensitive) flags |= REG_ICASE; if ((rep = malloc(sizeof(regex_t))) == NULL) err(1, "malloc"); if ((eval = regcomp(rep, re, flags)) != 0) errx(1, "%lu: %s: RE error: %s", linenum, fname, strregerror(eval, rep)); if (maxnsub < rep->re_nsub) maxnsub = rep->re_nsub; return (rep); } /* * Compile the substitution string of a regular expression and set res to * point to a saved copy of it. Nsub is the number of parenthesized regular * expressions. */ static char * compile_subst(char *p, struct s_subst *s) { static char lbuf[_POSIX2_LINE_MAX + 1]; int asize, size; u_char ref; char c, *text, *op, *sp; int more = 1, sawesc = 0; c = *p++; /* Terminator character */ if (c == '\0') return (NULL); s->maxbref = 0; s->linenum = linenum; asize = 2 * _POSIX2_LINE_MAX + 1; if ((text = malloc(asize)) == NULL) err(1, "malloc"); size = 0; do { op = sp = text + size; for (; *p; p++) { if (*p == '\\' || sawesc) { /* * If this is a continuation from the last * buffer, we won't have a character to * skip over. */ if (sawesc) sawesc = 0; else p++; if (*p == '\0') { /* * This escaped character is continued * in the next part of the line. Note * this fact, then cause the loop to * exit w/ normal EOL case and reenter * above with the new buffer. */ sawesc = 1; p--; continue; } else if (strchr("123456789", *p) != NULL) { *sp++ = '\\'; ref = *p - '0'; if (s->re != NULL && ref > s->re->re_nsub) errx(1, "%lu: %s: \\%c not defined in the RE", linenum, fname, *p); if (s->maxbref < ref) s->maxbref = ref; } else if (*p == '&' || *p == '\\') *sp++ = '\\'; } else if (*p == c) { if (*++p == '\0' && more) { if (cu_fgets(lbuf, sizeof(lbuf), &more)) p = lbuf; } *sp++ = '\0'; size += sp - op; if ((s->new = realloc(text, size)) == NULL) err(1, "realloc"); return (p); } else if (*p == '\n') { errx(1, "%lu: %s: unescaped newline inside substitute pattern", linenum, fname); /* NOTREACHED */ } *sp++ = *p; } size += sp - op; if (asize - size < _POSIX2_LINE_MAX + 1) { asize *= 2; if ((text = realloc(text, asize)) == NULL) err(1, "realloc"); } } while (cu_fgets(p = lbuf, sizeof(lbuf), &more)); errx(1, "%lu: %s: unterminated substitute in regular expression", linenum, fname); /* NOTREACHED */ } /* * Compile the flags of the s command */ static char * compile_flags(char *p, struct s_subst *s) { int gn; /* True if we have seen g or n */ unsigned long nval; char wfile[_POSIX2_LINE_MAX + 1], *q, *eq; s->n = 1; /* Default */ s->p = 0; s->wfile = NULL; s->wfd = -1; s->icase = 0; for (gn = 0;;) { EATSPACE(); /* EXTENSION */ switch (*p) { case 'g': if (gn) errx(1, "%lu: %s: more than one number or 'g' in substitute flags", linenum, fname); gn = 1; s->n = 0; break; case '\0': case '\n': case ';': return (p); case 'p': s->p = 1; break; case 'i': case 'I': s->icase = 1; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (gn) errx(1, "%lu: %s: more than one number or 'g' in substitute flags", linenum, fname); gn = 1; errno = 0; nval = strtol(p, &p, 10); if (errno == ERANGE || nval > INT_MAX) errx(1, "%lu: %s: overflow in the 'N' substitute flag", linenum, fname); s->n = nval; p--; break; case 'w': p++; #ifdef HISTORIC_PRACTICE if (*p != ' ') { warnx("%lu: %s: space missing before w wfile", linenum, fname); return (p); } #endif EATSPACE(); q = wfile; eq = wfile + sizeof(wfile) - 1; while (*p) { if (*p == '\n') break; if (q >= eq) err(1, "wfile too long"); *q++ = *p++; } *q = '\0'; if (q == wfile) errx(1, "%lu: %s: no wfile specified", linenum, fname); s->wfile = strdup(wfile); if (!aflag && (s->wfd = open(wfile, O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, DEFFILEMODE)) == -1) err(1, "%s", wfile); return (p); default: errx(1, "%lu: %s: bad flag in substitute command: '%c'", linenum, fname, *p); break; } p++; } } /* * Compile a translation set of strings into a lookup table. */ static char * compile_tr(char *p, struct s_tr **py) { struct s_tr *y; int i; const char *op, *np; char old[_POSIX2_LINE_MAX + 1]; char new[_POSIX2_LINE_MAX + 1]; size_t oclen, oldlen, nclen, newlen; mbstate_t mbs1, mbs2; if ((*py = y = malloc(sizeof(*y))) == NULL) err(1, NULL); y->multis = NULL; y->nmultis = 0; if (*p == '\0' || *p == '\\') errx(1, "%lu: %s: transform pattern can not be delimited by newline or backslash", linenum, fname); p = compile_delimited(p, old, 1); if (p == NULL) errx(1, "%lu: %s: unterminated transform source string", linenum, fname); p = compile_delimited(p - 1, new, 1); if (p == NULL) errx(1, "%lu: %s: unterminated transform target string", linenum, fname); EATSPACE(); op = old; oldlen = mbsrtowcs(NULL, &op, 0, NULL); if (oldlen == (size_t)-1) err(1, NULL); np = new; newlen = mbsrtowcs(NULL, &np, 0, NULL); if (newlen == (size_t)-1) err(1, NULL); if (newlen != oldlen) errx(1, "%lu: %s: transform strings are not the same length", linenum, fname); if (MB_CUR_MAX == 1) { /* * The single-byte encoding case is easy: generate a * lookup table. */ for (i = 0; i <= UCHAR_MAX; i++) y->bytetab[i] = (char)i; for (; *op; op++, np++) y->bytetab[(u_char)*op] = *np; } else { /* * Multi-byte encoding case: generate a lookup table as * above, but only for single-byte characters. The first * bytes of multi-byte characters have their lookup table * entries set to 0, which causes do_tr() to search through * an auxiliary vector of multi-byte mappings. */ memset(&mbs1, 0, sizeof(mbs1)); memset(&mbs2, 0, sizeof(mbs2)); for (i = 0; i <= UCHAR_MAX; i++) y->bytetab[i] = (btowc(i) != WEOF) ? i : 0; while (*op != '\0') { oclen = mbrlen(op, MB_LEN_MAX, &mbs1); if (oclen == (size_t)-1 || oclen == (size_t)-2) errc(1, EILSEQ, NULL); nclen = mbrlen(np, MB_LEN_MAX, &mbs2); if (nclen == (size_t)-1 || nclen == (size_t)-2) errc(1, EILSEQ, NULL); if (oclen == 1 && nclen == 1) y->bytetab[(u_char)*op] = *np; else { y->bytetab[(u_char)*op] = 0; y->multis = realloc(y->multis, (y->nmultis + 1) * sizeof(*y->multis)); if (y->multis == NULL) err(1, NULL); i = y->nmultis++; y->multis[i].fromlen = oclen; memcpy(y->multis[i].from, op, oclen); y->multis[i].tolen = nclen; memcpy(y->multis[i].to, np, nclen); } op += oclen; np += nclen; } } return (p); } /* - * Compile the text following an a or i command. + * Compile the text following an a, c, or i command. */ static char * compile_text(void) { int asize, esc_nl, size; char *text, *p, *op, *s; char lbuf[_POSIX2_LINE_MAX + 1]; asize = 2 * _POSIX2_LINE_MAX + 1; if ((text = malloc(asize)) == NULL) err(1, "malloc"); size = 0; while (cu_fgets(lbuf, sizeof(lbuf), NULL)) { op = s = text + size; p = lbuf; - EATSPACE(); for (esc_nl = 0; *p != '\0'; p++) { if (*p == '\\' && p[1] != '\0' && *++p == '\n') esc_nl = 1; *s++ = *p; } size += s - op; if (!esc_nl) { *s = '\0'; break; } if (asize - size < _POSIX2_LINE_MAX + 1) { asize *= 2; if ((text = realloc(text, asize)) == NULL) err(1, "realloc"); } } text[size] = '\0'; if ((p = realloc(text, size + 1)) == NULL) err(1, "realloc"); return (p); } /* * Get an address and return a pointer to the first character after * it. Fill the structure pointed to according to the address. */ static char * compile_addr(char *p, struct s_addr *a) { char *end, re[_POSIX2_LINE_MAX + 1]; int icase; icase = 0; a->type = 0; switch (*p) { case '\\': /* Context address */ ++p; /* FALLTHROUGH */ case '/': /* Context address */ p = compile_delimited(p, re, 0); if (p == NULL) errx(1, "%lu: %s: unterminated regular expression", linenum, fname); /* Check for case insensitive regexp flag */ if (*p == 'I') { icase = 1; p++; } if (*re == '\0') a->u.r = NULL; else a->u.r = compile_re(re, icase); a->type = AT_RE; return (p); case '$': /* Last line */ a->type = AT_LAST; return (p + 1); case '+': /* Relative line number */ a->type = AT_RELLINE; p++; /* FALLTHROUGH */ /* Line number */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (a->type == 0) a->type = AT_LINE; a->u.l = strtol(p, &end, 10); return (end); default: errx(1, "%lu: %s: expected context address", linenum, fname); return (NULL); } } /* * duptoeol -- * Return a copy of all the characters up to \n or \0. */ static char * duptoeol(char *s, const char *ctype) { size_t len; int ws; char *p, *start; ws = 0; for (start = s; *s != '\0' && *s != '\n'; ++s) ws = isspace((unsigned char)*s); *s = '\0'; if (ws) warnx("%lu: %s: whitespace after %s", linenum, fname, ctype); len = s - start + 1; if ((p = malloc(len)) == NULL) err(1, "malloc"); return (memmove(p, start, len)); } /* * Convert goto label names to addresses, and count a and r commands, in * the given subset of the script. Free the memory used by labels in b * and t commands (but not by :). * * TODO: Remove } nodes */ static void fixuplabel(struct s_command *cp, struct s_command *end) { for (; cp != end; cp = cp->next) switch (cp->code) { case 'a': case 'r': appendnum++; break; case 'b': case 't': /* Resolve branch target. */ if (cp->t == NULL) { cp->u.c = NULL; break; } if ((cp->u.c = findlabel(cp->t)) == NULL) errx(1, "%lu: %s: undefined label '%s'", linenum, fname, cp->t); free(cp->t); break; case '{': /* Do interior commands. */ fixuplabel(cp->u.c, cp->next); break; } } /* * Associate the given command label for later lookup. */ static void enterlabel(struct s_command *cp) { struct labhash **lhp, *lh; u_char *p; u_int h, c; for (h = 0, p = (u_char *)cp->t; (c = *p) != 0; p++) h = (h << 5) + h + c; lhp = &labels[h & LHMASK]; for (lh = *lhp; lh != NULL; lh = lh->lh_next) if (lh->lh_hash == h && strcmp(cp->t, lh->lh_cmd->t) == 0) errx(1, "%lu: %s: duplicate label '%s'", linenum, fname, cp->t); if ((lh = malloc(sizeof *lh)) == NULL) err(1, "malloc"); lh->lh_next = *lhp; lh->lh_hash = h; lh->lh_cmd = cp; lh->lh_ref = 0; *lhp = lh; } /* * Find the label contained in the command l in the command linked * list cp. L is excluded from the search. Return NULL if not found. */ static struct s_command * findlabel(char *name) { struct labhash *lh; u_char *p; u_int h, c; for (h = 0, p = (u_char *)name; (c = *p) != 0; p++) h = (h << 5) + h + c; for (lh = labels[h & LHMASK]; lh != NULL; lh = lh->lh_next) { if (lh->lh_hash == h && strcmp(name, lh->lh_cmd->t) == 0) { lh->lh_ref = 1; return (lh->lh_cmd); } } return (NULL); } /* * Warn about any unused labels. As a side effect, release the label hash * table space. */ static void uselabel(void) { struct labhash *lh, *next; int i; for (i = 0; i < LHSZ; i++) { for (lh = labels[i]; lh != NULL; lh = next) { next = lh->lh_next; if (!lh->lh_ref) warnx("%lu: %s: unused label '%s'", linenum, fname, lh->lh_cmd->t); free(lh); } } } Index: projects/release-pkg/usr.sbin/bhyve/pci_ahci.c =================================================================== --- projects/release-pkg/usr.sbin/bhyve/pci_ahci.c (revision 297604) +++ projects/release-pkg/usr.sbin/bhyve/pci_ahci.c (revision 297605) @@ -1,2351 +1,2347 @@ /*- * Copyright (c) 2013 Zhixiang Yu * 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 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$ */ #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 "bhyverun.h" #include "pci_emul.h" #include "ahci.h" #include "block_if.h" #define MAX_PORTS 6 /* Intel ICH8 AHCI supports 6 ports */ #define PxSIG_ATA 0x00000101 /* ATA drive */ #define PxSIG_ATAPI 0xeb140101 /* ATAPI drive */ enum sata_fis_type { FIS_TYPE_REGH2D = 0x27, /* Register FIS - host to device */ FIS_TYPE_REGD2H = 0x34, /* Register FIS - device to host */ FIS_TYPE_DMAACT = 0x39, /* DMA activate FIS - device to host */ FIS_TYPE_DMASETUP = 0x41, /* DMA setup FIS - bidirectional */ FIS_TYPE_DATA = 0x46, /* Data FIS - bidirectional */ FIS_TYPE_BIST = 0x58, /* BIST activate FIS - bidirectional */ FIS_TYPE_PIOSETUP = 0x5F, /* PIO setup FIS - device to host */ FIS_TYPE_SETDEVBITS = 0xA1, /* Set dev bits FIS - device to host */ }; /* * SCSI opcodes */ #define TEST_UNIT_READY 0x00 #define REQUEST_SENSE 0x03 #define INQUIRY 0x12 #define START_STOP_UNIT 0x1B #define PREVENT_ALLOW 0x1E #define READ_CAPACITY 0x25 #define READ_10 0x28 #define POSITION_TO_ELEMENT 0x2B #define READ_TOC 0x43 #define GET_EVENT_STATUS_NOTIFICATION 0x4A #define MODE_SENSE_10 0x5A #define REPORT_LUNS 0xA0 #define READ_12 0xA8 #define READ_CD 0xBE /* * SCSI mode page codes */ #define MODEPAGE_RW_ERROR_RECOVERY 0x01 #define MODEPAGE_CD_CAPABILITIES 0x2A /* * ATA commands */ #define ATA_SF_ENAB_SATA_SF 0x10 #define ATA_SATA_SF_AN 0x05 #define ATA_SF_DIS_SATA_SF 0x90 /* * Debug printf */ #ifdef AHCI_DEBUG static FILE *dbg; #define DPRINTF(format, arg...) do{fprintf(dbg, format, ##arg);fflush(dbg);}while(0) #else #define DPRINTF(format, arg...) #endif #define WPRINTF(format, arg...) printf(format, ##arg) struct ahci_ioreq { struct blockif_req io_req; struct ahci_port *io_pr; STAILQ_ENTRY(ahci_ioreq) io_flist; TAILQ_ENTRY(ahci_ioreq) io_blist; uint8_t *cfis; uint32_t len; uint32_t done; int slot; int more; }; struct ahci_port { struct blockif_ctxt *bctx; struct pci_ahci_softc *pr_sc; uint8_t *cmd_lst; uint8_t *rfis; char ident[20 + 1]; int atapi; int reset; int waitforclear; int mult_sectors; uint8_t xfermode; uint8_t err_cfis[20]; uint8_t sense_key; uint8_t asc; u_int ccs; uint32_t pending; uint32_t clb; uint32_t clbu; uint32_t fb; uint32_t fbu; uint32_t is; uint32_t ie; uint32_t cmd; uint32_t unused0; uint32_t tfd; uint32_t sig; uint32_t ssts; uint32_t sctl; uint32_t serr; uint32_t sact; uint32_t ci; uint32_t sntf; uint32_t fbs; /* * i/o request info */ struct ahci_ioreq *ioreq; int ioqsz; STAILQ_HEAD(ahci_fhead, ahci_ioreq) iofhd; TAILQ_HEAD(ahci_bhead, ahci_ioreq) iobhd; }; struct ahci_cmd_hdr { uint16_t flags; uint16_t prdtl; uint32_t prdbc; uint64_t ctba; uint32_t reserved[4]; }; struct ahci_prdt_entry { uint64_t dba; uint32_t reserved; #define DBCMASK 0x3fffff uint32_t dbc; }; struct pci_ahci_softc { struct pci_devinst *asc_pi; pthread_mutex_t mtx; int ports; uint32_t cap; uint32_t ghc; uint32_t is; uint32_t pi; uint32_t vs; uint32_t ccc_ctl; uint32_t ccc_pts; uint32_t em_loc; uint32_t em_ctl; uint32_t cap2; uint32_t bohc; uint32_t lintr; struct ahci_port port[MAX_PORTS]; }; #define ahci_ctx(sc) ((sc)->asc_pi->pi_vmctx) static void ahci_handle_port(struct ahci_port *p); static inline void lba_to_msf(uint8_t *buf, int lba) { lba += 150; buf[0] = (lba / 75) / 60; buf[1] = (lba / 75) % 60; buf[2] = lba % 75; } /* * generate HBA intr depending on whether or not ports within * the controller have an interrupt pending. */ static void ahci_generate_intr(struct pci_ahci_softc *sc) { struct pci_devinst *pi; int i; pi = sc->asc_pi; for (i = 0; i < sc->ports; i++) { struct ahci_port *pr; pr = &sc->port[i]; if (pr->is & pr->ie) sc->is |= (1 << i); } DPRINTF("%s %x\n", __func__, sc->is); if (sc->is && (sc->ghc & AHCI_GHC_IE)) { if (pci_msi_enabled(pi)) { /* * Generate an MSI interrupt on every edge */ pci_generate_msi(pi, 0); } else if (!sc->lintr) { /* * Only generate a pin-based interrupt if one wasn't * in progress */ sc->lintr = 1; pci_lintr_assert(pi); } } else if (sc->lintr) { /* * No interrupts: deassert pin-based signal if it had * been asserted */ pci_lintr_deassert(pi); sc->lintr = 0; } } static void ahci_write_fis(struct ahci_port *p, enum sata_fis_type ft, uint8_t *fis) { int offset, len, irq; if (p->rfis == NULL || !(p->cmd & AHCI_P_CMD_FRE)) return; switch (ft) { case FIS_TYPE_REGD2H: offset = 0x40; len = 20; irq = (fis[1] & (1 << 6)) ? AHCI_P_IX_DHR : 0; break; case FIS_TYPE_SETDEVBITS: offset = 0x58; len = 8; irq = (fis[1] & (1 << 6)) ? AHCI_P_IX_SDB : 0; break; case FIS_TYPE_PIOSETUP: offset = 0x20; len = 20; irq = (fis[1] & (1 << 6)) ? AHCI_P_IX_PS : 0; break; default: WPRINTF("unsupported fis type %d\n", ft); return; } if (fis[2] & ATA_S_ERROR) { p->waitforclear = 1; irq |= AHCI_P_IX_TFE; } memcpy(p->rfis + offset, fis, len); if (irq) { p->is |= irq; ahci_generate_intr(p->pr_sc); } } static void ahci_write_fis_piosetup(struct ahci_port *p) { uint8_t fis[20]; memset(fis, 0, sizeof(fis)); fis[0] = FIS_TYPE_PIOSETUP; ahci_write_fis(p, FIS_TYPE_PIOSETUP, fis); } static void ahci_write_fis_sdb(struct ahci_port *p, int slot, uint8_t *cfis, uint32_t tfd) { uint8_t fis[8]; uint8_t error; error = (tfd >> 8) & 0xff; tfd &= 0x77; memset(fis, 0, sizeof(fis)); fis[0] = FIS_TYPE_SETDEVBITS; fis[1] = (1 << 6); fis[2] = tfd; fis[3] = error; if (fis[2] & ATA_S_ERROR) { p->err_cfis[0] = slot; p->err_cfis[2] = tfd; p->err_cfis[3] = error; memcpy(&p->err_cfis[4], cfis + 4, 16); } else { *(uint32_t *)(fis + 4) = (1 << slot); p->sact &= ~(1 << slot); } p->tfd &= ~0x77; p->tfd |= tfd; ahci_write_fis(p, FIS_TYPE_SETDEVBITS, fis); } static void ahci_write_fis_d2h(struct ahci_port *p, int slot, uint8_t *cfis, uint32_t tfd) { uint8_t fis[20]; uint8_t error; error = (tfd >> 8) & 0xff; memset(fis, 0, sizeof(fis)); fis[0] = FIS_TYPE_REGD2H; fis[1] = (1 << 6); fis[2] = tfd & 0xff; fis[3] = error; fis[4] = cfis[4]; fis[5] = cfis[5]; fis[6] = cfis[6]; fis[7] = cfis[7]; fis[8] = cfis[8]; fis[9] = cfis[9]; fis[10] = cfis[10]; fis[11] = cfis[11]; fis[12] = cfis[12]; fis[13] = cfis[13]; if (fis[2] & ATA_S_ERROR) { p->err_cfis[0] = 0x80; p->err_cfis[2] = tfd & 0xff; p->err_cfis[3] = error; memcpy(&p->err_cfis[4], cfis + 4, 16); } else p->ci &= ~(1 << slot); p->tfd = tfd; ahci_write_fis(p, FIS_TYPE_REGD2H, fis); } static void ahci_write_fis_d2h_ncq(struct ahci_port *p, int slot) { uint8_t fis[20]; p->tfd = ATA_S_READY | ATA_S_DSC; memset(fis, 0, sizeof(fis)); fis[0] = FIS_TYPE_REGD2H; fis[1] = 0; /* No interrupt */ fis[2] = p->tfd; /* Status */ fis[3] = 0; /* No error */ p->ci &= ~(1 << slot); ahci_write_fis(p, FIS_TYPE_REGD2H, fis); } static void ahci_write_reset_fis_d2h(struct ahci_port *p) { uint8_t fis[20]; memset(fis, 0, sizeof(fis)); fis[0] = FIS_TYPE_REGD2H; fis[3] = 1; fis[4] = 1; if (p->atapi) { fis[5] = 0x14; fis[6] = 0xeb; } fis[12] = 1; ahci_write_fis(p, FIS_TYPE_REGD2H, fis); } static void ahci_check_stopped(struct ahci_port *p) { /* * If we are no longer processing the command list and nothing * is in-flight, clear the running bit, the current command * slot, the command issue and active bits. */ if (!(p->cmd & AHCI_P_CMD_ST)) { if (p->pending == 0) { p->ccs = 0; p->cmd &= ~(AHCI_P_CMD_CR | AHCI_P_CMD_CCS_MASK); p->ci = 0; p->sact = 0; p->waitforclear = 0; } } } static void ahci_port_stop(struct ahci_port *p) { struct ahci_ioreq *aior; uint8_t *cfis; int slot; - int ncq; int error; assert(pthread_mutex_isowned_np(&p->pr_sc->mtx)); TAILQ_FOREACH(aior, &p->iobhd, io_blist) { /* * Try to cancel the outstanding blockif request. */ error = blockif_cancel(p->bctx, &aior->io_req); if (error != 0) continue; slot = aior->slot; cfis = aior->cfis; if (cfis[2] == ATA_WRITE_FPDMA_QUEUED || cfis[2] == ATA_READ_FPDMA_QUEUED || cfis[2] == ATA_SEND_FPDMA_QUEUED) - ncq = 1; - - if (ncq) - p->sact &= ~(1 << slot); + p->sact &= ~(1 << slot); /* NCQ */ else p->ci &= ~(1 << slot); /* * This command is now done. */ p->pending &= ~(1 << slot); /* * Delete the blockif request from the busy list */ TAILQ_REMOVE(&p->iobhd, aior, io_blist); /* * Move the blockif request back to the free list */ STAILQ_INSERT_TAIL(&p->iofhd, aior, io_flist); } ahci_check_stopped(p); } static void ahci_port_reset(struct ahci_port *pr) { pr->serr = 0; pr->sact = 0; pr->xfermode = ATA_UDMA6; pr->mult_sectors = 128; if (!pr->bctx) { pr->ssts = ATA_SS_DET_NO_DEVICE; pr->sig = 0xFFFFFFFF; pr->tfd = 0x7F; return; } pr->ssts = ATA_SS_DET_PHY_ONLINE | ATA_SS_IPM_ACTIVE; if (pr->sctl & ATA_SC_SPD_MASK) pr->ssts |= (pr->sctl & ATA_SC_SPD_MASK); else pr->ssts |= ATA_SS_SPD_GEN3; pr->tfd = (1 << 8) | ATA_S_DSC | ATA_S_DMA; if (!pr->atapi) { pr->sig = PxSIG_ATA; pr->tfd |= ATA_S_READY; } else pr->sig = PxSIG_ATAPI; ahci_write_reset_fis_d2h(pr); } static void ahci_reset(struct pci_ahci_softc *sc) { int i; sc->ghc = AHCI_GHC_AE; sc->is = 0; if (sc->lintr) { pci_lintr_deassert(sc->asc_pi); sc->lintr = 0; } for (i = 0; i < sc->ports; i++) { sc->port[i].ie = 0; sc->port[i].is = 0; sc->port[i].cmd = (AHCI_P_CMD_SUD | AHCI_P_CMD_POD); if (sc->port[i].bctx) sc->port[i].cmd |= AHCI_P_CMD_CPS; sc->port[i].sctl = 0; ahci_port_reset(&sc->port[i]); } } static void ata_string(uint8_t *dest, const char *src, int len) { int i; for (i = 0; i < len; i++) { if (*src) dest[i ^ 1] = *src++; else dest[i ^ 1] = ' '; } } static void atapi_string(uint8_t *dest, const char *src, int len) { int i; for (i = 0; i < len; i++) { if (*src) dest[i] = *src++; else dest[i] = ' '; } } /* * Build up the iovec based on the PRDT, 'done' and 'len'. */ static void ahci_build_iov(struct ahci_port *p, struct ahci_ioreq *aior, struct ahci_prdt_entry *prdt, uint16_t prdtl) { struct blockif_req *breq = &aior->io_req; int i, j, skip, todo, left, extra; uint32_t dbcsz; /* Copy part of PRDT between 'done' and 'len' bytes into the iov. */ skip = aior->done; left = aior->len - aior->done; todo = 0; for (i = 0, j = 0; i < prdtl && j < BLOCKIF_IOV_MAX && left > 0; i++, prdt++) { dbcsz = (prdt->dbc & DBCMASK) + 1; /* Skip already done part of the PRDT */ if (dbcsz <= skip) { skip -= dbcsz; continue; } dbcsz -= skip; if (dbcsz > left) dbcsz = left; breq->br_iov[j].iov_base = paddr_guest2host(ahci_ctx(p->pr_sc), prdt->dba + skip, dbcsz); breq->br_iov[j].iov_len = dbcsz; todo += dbcsz; left -= dbcsz; skip = 0; j++; } /* If we got limited by IOV length, round I/O down to sector size. */ if (j == BLOCKIF_IOV_MAX) { extra = todo % blockif_sectsz(p->bctx); todo -= extra; assert(todo > 0); while (extra > 0) { if (breq->br_iov[j - 1].iov_len > extra) { breq->br_iov[j - 1].iov_len -= extra; break; } extra -= breq->br_iov[j - 1].iov_len; j--; } } breq->br_iovcnt = j; breq->br_resid = todo; aior->done += todo; aior->more = (aior->done < aior->len && i < prdtl); } static void ahci_handle_rw(struct ahci_port *p, int slot, uint8_t *cfis, uint32_t done) { struct ahci_ioreq *aior; struct blockif_req *breq; struct ahci_prdt_entry *prdt; struct ahci_cmd_hdr *hdr; uint64_t lba; uint32_t len; int err, first, ncq, readop; prdt = (struct ahci_prdt_entry *)(cfis + 0x80); hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE); ncq = 0; readop = 1; first = (done == 0); if (cfis[2] == ATA_WRITE || cfis[2] == ATA_WRITE48 || cfis[2] == ATA_WRITE_MUL || cfis[2] == ATA_WRITE_MUL48 || cfis[2] == ATA_WRITE_DMA || cfis[2] == ATA_WRITE_DMA48 || cfis[2] == ATA_WRITE_FPDMA_QUEUED) readop = 0; if (cfis[2] == ATA_WRITE_FPDMA_QUEUED || cfis[2] == ATA_READ_FPDMA_QUEUED) { lba = ((uint64_t)cfis[10] << 40) | ((uint64_t)cfis[9] << 32) | ((uint64_t)cfis[8] << 24) | ((uint64_t)cfis[6] << 16) | ((uint64_t)cfis[5] << 8) | cfis[4]; len = cfis[11] << 8 | cfis[3]; if (!len) len = 65536; ncq = 1; } else if (cfis[2] == ATA_READ48 || cfis[2] == ATA_WRITE48 || cfis[2] == ATA_READ_MUL48 || cfis[2] == ATA_WRITE_MUL48 || cfis[2] == ATA_READ_DMA48 || cfis[2] == ATA_WRITE_DMA48) { lba = ((uint64_t)cfis[10] << 40) | ((uint64_t)cfis[9] << 32) | ((uint64_t)cfis[8] << 24) | ((uint64_t)cfis[6] << 16) | ((uint64_t)cfis[5] << 8) | cfis[4]; len = cfis[13] << 8 | cfis[12]; if (!len) len = 65536; } else { lba = ((cfis[7] & 0xf) << 24) | (cfis[6] << 16) | (cfis[5] << 8) | cfis[4]; len = cfis[12]; if (!len) len = 256; } lba *= blockif_sectsz(p->bctx); len *= blockif_sectsz(p->bctx); /* Pull request off free list */ aior = STAILQ_FIRST(&p->iofhd); assert(aior != NULL); STAILQ_REMOVE_HEAD(&p->iofhd, io_flist); aior->cfis = cfis; aior->slot = slot; aior->len = len; aior->done = done; breq = &aior->io_req; breq->br_offset = lba + done; ahci_build_iov(p, aior, prdt, hdr->prdtl); /* Mark this command in-flight. */ p->pending |= 1 << slot; /* Stuff request onto busy list. */ TAILQ_INSERT_HEAD(&p->iobhd, aior, io_blist); if (ncq && first) ahci_write_fis_d2h_ncq(p, slot); if (readop) err = blockif_read(p->bctx, breq); else err = blockif_write(p->bctx, breq); assert(err == 0); } static void ahci_handle_flush(struct ahci_port *p, int slot, uint8_t *cfis) { struct ahci_ioreq *aior; struct blockif_req *breq; int err; /* * Pull request off free list */ aior = STAILQ_FIRST(&p->iofhd); assert(aior != NULL); STAILQ_REMOVE_HEAD(&p->iofhd, io_flist); aior->cfis = cfis; aior->slot = slot; aior->len = 0; aior->done = 0; aior->more = 0; breq = &aior->io_req; /* * Mark this command in-flight. */ p->pending |= 1 << slot; /* * Stuff request onto busy list */ TAILQ_INSERT_HEAD(&p->iobhd, aior, io_blist); err = blockif_flush(p->bctx, breq); assert(err == 0); } static inline void read_prdt(struct ahci_port *p, int slot, uint8_t *cfis, void *buf, int size) { struct ahci_cmd_hdr *hdr; struct ahci_prdt_entry *prdt; void *to; int i, len; hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE); len = size; to = buf; prdt = (struct ahci_prdt_entry *)(cfis + 0x80); for (i = 0; i < hdr->prdtl && len; i++) { uint8_t *ptr; uint32_t dbcsz; int sublen; dbcsz = (prdt->dbc & DBCMASK) + 1; ptr = paddr_guest2host(ahci_ctx(p->pr_sc), prdt->dba, dbcsz); sublen = len < dbcsz ? len : dbcsz; memcpy(to, ptr, sublen); len -= sublen; to += sublen; prdt++; } } static void ahci_handle_dsm_trim(struct ahci_port *p, int slot, uint8_t *cfis, uint32_t done) { struct ahci_ioreq *aior; struct blockif_req *breq; uint8_t *entry; uint64_t elba; uint32_t len, elen; int err, first, ncq; uint8_t buf[512]; first = (done == 0); if (cfis[2] == ATA_DATA_SET_MANAGEMENT) { len = (uint16_t)cfis[13] << 8 | cfis[12]; len *= 512; ncq = 0; } else { /* ATA_SEND_FPDMA_QUEUED */ len = (uint16_t)cfis[11] << 8 | cfis[3]; len *= 512; ncq = 1; } read_prdt(p, slot, cfis, buf, sizeof(buf)); next: entry = &buf[done]; elba = ((uint64_t)entry[5] << 40) | ((uint64_t)entry[4] << 32) | ((uint64_t)entry[3] << 24) | ((uint64_t)entry[2] << 16) | ((uint64_t)entry[1] << 8) | entry[0]; elen = (uint16_t)entry[7] << 8 | entry[6]; done += 8; if (elen == 0) { if (done >= len) { ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); p->pending &= ~(1 << slot); ahci_check_stopped(p); if (!first) ahci_handle_port(p); return; } goto next; } /* * Pull request off free list */ aior = STAILQ_FIRST(&p->iofhd); assert(aior != NULL); STAILQ_REMOVE_HEAD(&p->iofhd, io_flist); aior->cfis = cfis; aior->slot = slot; aior->len = len; aior->done = done; aior->more = (len != done); breq = &aior->io_req; breq->br_offset = elba * blockif_sectsz(p->bctx); breq->br_resid = elen * blockif_sectsz(p->bctx); /* * Mark this command in-flight. */ p->pending |= 1 << slot; /* * Stuff request onto busy list */ TAILQ_INSERT_HEAD(&p->iobhd, aior, io_blist); if (ncq && first) ahci_write_fis_d2h_ncq(p, slot); err = blockif_delete(p->bctx, breq); assert(err == 0); } static inline void write_prdt(struct ahci_port *p, int slot, uint8_t *cfis, void *buf, int size) { struct ahci_cmd_hdr *hdr; struct ahci_prdt_entry *prdt; void *from; int i, len; hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE); len = size; from = buf; prdt = (struct ahci_prdt_entry *)(cfis + 0x80); for (i = 0; i < hdr->prdtl && len; i++) { uint8_t *ptr; uint32_t dbcsz; int sublen; dbcsz = (prdt->dbc & DBCMASK) + 1; ptr = paddr_guest2host(ahci_ctx(p->pr_sc), prdt->dba, dbcsz); sublen = len < dbcsz ? len : dbcsz; memcpy(ptr, from, sublen); len -= sublen; from += sublen; prdt++; } hdr->prdbc = size - len; } static void ahci_checksum(uint8_t *buf, int size) { int i; uint8_t sum = 0; for (i = 0; i < size - 1; i++) sum += buf[i]; buf[size - 1] = 0x100 - sum; } static void ahci_handle_read_log(struct ahci_port *p, int slot, uint8_t *cfis) { struct ahci_cmd_hdr *hdr; uint8_t buf[512]; hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE); if (p->atapi || hdr->prdtl == 0 || cfis[4] != 0x10 || cfis[5] != 0 || cfis[9] != 0 || cfis[12] != 1 || cfis[13] != 0) { ahci_write_fis_d2h(p, slot, cfis, (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR); return; } memset(buf, 0, sizeof(buf)); memcpy(buf, p->err_cfis, sizeof(p->err_cfis)); ahci_checksum(buf, sizeof(buf)); if (cfis[2] == ATA_READ_LOG_EXT) ahci_write_fis_piosetup(p); write_prdt(p, slot, cfis, (void *)buf, sizeof(buf)); ahci_write_fis_d2h(p, slot, cfis, ATA_S_DSC | ATA_S_READY); } static void handle_identify(struct ahci_port *p, int slot, uint8_t *cfis) { struct ahci_cmd_hdr *hdr; hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE); if (p->atapi || hdr->prdtl == 0) { ahci_write_fis_d2h(p, slot, cfis, (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR); } else { uint16_t buf[256]; uint64_t sectors; int sectsz, psectsz, psectoff, candelete, ro; uint16_t cyl; uint8_t sech, heads; ro = blockif_is_ro(p->bctx); candelete = blockif_candelete(p->bctx); sectsz = blockif_sectsz(p->bctx); sectors = blockif_size(p->bctx) / sectsz; blockif_chs(p->bctx, &cyl, &heads, &sech); blockif_psectsz(p->bctx, &psectsz, &psectoff); memset(buf, 0, sizeof(buf)); buf[0] = 0x0040; buf[1] = cyl; buf[3] = heads; buf[6] = sech; ata_string((uint8_t *)(buf+10), p->ident, 20); ata_string((uint8_t *)(buf+23), "001", 8); ata_string((uint8_t *)(buf+27), "BHYVE SATA DISK", 40); buf[47] = (0x8000 | 128); buf[48] = 0; buf[49] = (1 << 8 | 1 << 9 | 1 << 11); buf[50] = (1 << 14); buf[53] = (1 << 1 | 1 << 2); if (p->mult_sectors) buf[59] = (0x100 | p->mult_sectors); if (sectors <= 0x0fffffff) { buf[60] = sectors; buf[61] = (sectors >> 16); } else { buf[60] = 0xffff; buf[61] = 0x0fff; } buf[63] = 0x7; if (p->xfermode & ATA_WDMA0) buf[63] |= (1 << ((p->xfermode & 7) + 8)); buf[64] = 0x3; buf[65] = 120; buf[66] = 120; buf[67] = 120; buf[68] = 120; buf[69] = 0; buf[75] = 31; buf[76] = (ATA_SATA_GEN1 | ATA_SATA_GEN2 | ATA_SATA_GEN3 | ATA_SUPPORT_NCQ); buf[77] = (ATA_SUPPORT_RCVSND_FPDMA_QUEUED | (p->ssts & ATA_SS_SPD_MASK) >> 3); buf[80] = 0x3f0; buf[81] = 0x28; buf[82] = (ATA_SUPPORT_POWERMGT | ATA_SUPPORT_WRITECACHE| ATA_SUPPORT_LOOKAHEAD | ATA_SUPPORT_NOP); buf[83] = (ATA_SUPPORT_ADDRESS48 | ATA_SUPPORT_FLUSHCACHE | ATA_SUPPORT_FLUSHCACHE48 | 1 << 14); buf[84] = (1 << 14); buf[85] = (ATA_SUPPORT_POWERMGT | ATA_SUPPORT_WRITECACHE| ATA_SUPPORT_LOOKAHEAD | ATA_SUPPORT_NOP); buf[86] = (ATA_SUPPORT_ADDRESS48 | ATA_SUPPORT_FLUSHCACHE | ATA_SUPPORT_FLUSHCACHE48 | 1 << 15); buf[87] = (1 << 14); buf[88] = 0x7f; if (p->xfermode & ATA_UDMA0) buf[88] |= (1 << ((p->xfermode & 7) + 8)); buf[100] = sectors; buf[101] = (sectors >> 16); buf[102] = (sectors >> 32); buf[103] = (sectors >> 48); if (candelete && !ro) { buf[69] |= ATA_SUPPORT_RZAT | ATA_SUPPORT_DRAT; buf[105] = 1; buf[169] = ATA_SUPPORT_DSM_TRIM; } buf[106] = 0x4000; buf[209] = 0x4000; if (psectsz > sectsz) { buf[106] |= 0x2000; buf[106] |= ffsl(psectsz / sectsz) - 1; buf[209] |= (psectoff / sectsz); } if (sectsz > 512) { buf[106] |= 0x1000; buf[117] = sectsz / 2; buf[118] = ((sectsz / 2) >> 16); } buf[119] = (ATA_SUPPORT_RWLOGDMAEXT | 1 << 14); buf[120] = (ATA_SUPPORT_RWLOGDMAEXT | 1 << 14); buf[222] = 0x1020; buf[255] = 0x00a5; ahci_checksum((uint8_t *)buf, sizeof(buf)); ahci_write_fis_piosetup(p); write_prdt(p, slot, cfis, (void *)buf, sizeof(buf)); ahci_write_fis_d2h(p, slot, cfis, ATA_S_DSC | ATA_S_READY); } } static void handle_atapi_identify(struct ahci_port *p, int slot, uint8_t *cfis) { if (!p->atapi) { ahci_write_fis_d2h(p, slot, cfis, (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR); } else { uint16_t buf[256]; memset(buf, 0, sizeof(buf)); buf[0] = (2 << 14 | 5 << 8 | 1 << 7 | 2 << 5); ata_string((uint8_t *)(buf+10), p->ident, 20); ata_string((uint8_t *)(buf+23), "001", 8); ata_string((uint8_t *)(buf+27), "BHYVE SATA DVD ROM", 40); buf[49] = (1 << 9 | 1 << 8); buf[50] = (1 << 14 | 1); buf[53] = (1 << 2 | 1 << 1); buf[62] = 0x3f; buf[63] = 7; if (p->xfermode & ATA_WDMA0) buf[63] |= (1 << ((p->xfermode & 7) + 8)); buf[64] = 3; buf[65] = 120; buf[66] = 120; buf[67] = 120; buf[68] = 120; buf[76] = (ATA_SATA_GEN1 | ATA_SATA_GEN2 | ATA_SATA_GEN3); buf[77] = ((p->ssts & ATA_SS_SPD_MASK) >> 3); buf[78] = (1 << 5); buf[80] = 0x3f0; buf[82] = (ATA_SUPPORT_POWERMGT | ATA_SUPPORT_PACKET | ATA_SUPPORT_RESET | ATA_SUPPORT_NOP); buf[83] = (1 << 14); buf[84] = (1 << 14); buf[85] = (ATA_SUPPORT_POWERMGT | ATA_SUPPORT_PACKET | ATA_SUPPORT_RESET | ATA_SUPPORT_NOP); buf[87] = (1 << 14); buf[88] = 0x7f; if (p->xfermode & ATA_UDMA0) buf[88] |= (1 << ((p->xfermode & 7) + 8)); buf[222] = 0x1020; buf[255] = 0x00a5; ahci_checksum((uint8_t *)buf, sizeof(buf)); ahci_write_fis_piosetup(p); write_prdt(p, slot, cfis, (void *)buf, sizeof(buf)); ahci_write_fis_d2h(p, slot, cfis, ATA_S_DSC | ATA_S_READY); } } static void atapi_inquiry(struct ahci_port *p, int slot, uint8_t *cfis) { uint8_t buf[36]; uint8_t *acmd; int len; uint32_t tfd; acmd = cfis + 0x40; if (acmd[1] & 1) { /* VPD */ if (acmd[2] == 0) { /* Supported VPD pages */ buf[0] = 0x05; buf[1] = 0; buf[2] = 0; buf[3] = 1; buf[4] = 0; len = 4 + buf[3]; } else { p->sense_key = ATA_SENSE_ILLEGAL_REQUEST; p->asc = 0x24; tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR; cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, tfd); return; } } else { buf[0] = 0x05; buf[1] = 0x80; buf[2] = 0x00; buf[3] = 0x21; buf[4] = 31; buf[5] = 0; buf[6] = 0; buf[7] = 0; atapi_string(buf + 8, "BHYVE", 8); atapi_string(buf + 16, "BHYVE DVD-ROM", 16); atapi_string(buf + 32, "001", 4); len = sizeof(buf); } if (len > acmd[4]) len = acmd[4]; cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; write_prdt(p, slot, cfis, buf, len); ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); } static void atapi_read_capacity(struct ahci_port *p, int slot, uint8_t *cfis) { uint8_t buf[8]; uint64_t sectors; sectors = blockif_size(p->bctx) / 2048; be32enc(buf, sectors - 1); be32enc(buf + 4, 2048); cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; write_prdt(p, slot, cfis, buf, sizeof(buf)); ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); } static void atapi_read_toc(struct ahci_port *p, int slot, uint8_t *cfis) { uint8_t *acmd; uint8_t format; int len; acmd = cfis + 0x40; len = be16dec(acmd + 7); format = acmd[9] >> 6; switch (format) { case 0: { int msf, size; uint64_t sectors; uint8_t start_track, buf[20], *bp; msf = (acmd[1] >> 1) & 1; start_track = acmd[6]; if (start_track > 1 && start_track != 0xaa) { uint32_t tfd; p->sense_key = ATA_SENSE_ILLEGAL_REQUEST; p->asc = 0x24; tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR; cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, tfd); return; } bp = buf + 2; *bp++ = 1; *bp++ = 1; if (start_track <= 1) { *bp++ = 0; *bp++ = 0x14; *bp++ = 1; *bp++ = 0; if (msf) { *bp++ = 0; lba_to_msf(bp, 0); bp += 3; } else { *bp++ = 0; *bp++ = 0; *bp++ = 0; *bp++ = 0; } } *bp++ = 0; *bp++ = 0x14; *bp++ = 0xaa; *bp++ = 0; sectors = blockif_size(p->bctx) / blockif_sectsz(p->bctx); sectors >>= 2; if (msf) { *bp++ = 0; lba_to_msf(bp, sectors); bp += 3; } else { be32enc(bp, sectors); bp += 4; } size = bp - buf; be16enc(buf, size - 2); if (len > size) len = size; write_prdt(p, slot, cfis, buf, len); cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); break; } case 1: { uint8_t buf[12]; memset(buf, 0, sizeof(buf)); buf[1] = 0xa; buf[2] = 0x1; buf[3] = 0x1; if (len > sizeof(buf)) len = sizeof(buf); write_prdt(p, slot, cfis, buf, len); cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); break; } case 2: { int msf, size; uint64_t sectors; uint8_t *bp, buf[50]; msf = (acmd[1] >> 1) & 1; bp = buf + 2; *bp++ = 1; *bp++ = 1; *bp++ = 1; *bp++ = 0x14; *bp++ = 0; *bp++ = 0xa0; *bp++ = 0; *bp++ = 0; *bp++ = 0; *bp++ = 0; *bp++ = 1; *bp++ = 0; *bp++ = 0; *bp++ = 1; *bp++ = 0x14; *bp++ = 0; *bp++ = 0xa1; *bp++ = 0; *bp++ = 0; *bp++ = 0; *bp++ = 0; *bp++ = 1; *bp++ = 0; *bp++ = 0; *bp++ = 1; *bp++ = 0x14; *bp++ = 0; *bp++ = 0xa2; *bp++ = 0; *bp++ = 0; *bp++ = 0; sectors = blockif_size(p->bctx) / blockif_sectsz(p->bctx); sectors >>= 2; if (msf) { *bp++ = 0; lba_to_msf(bp, sectors); bp += 3; } else { be32enc(bp, sectors); bp += 4; } *bp++ = 1; *bp++ = 0x14; *bp++ = 0; *bp++ = 1; *bp++ = 0; *bp++ = 0; *bp++ = 0; if (msf) { *bp++ = 0; lba_to_msf(bp, 0); bp += 3; } else { *bp++ = 0; *bp++ = 0; *bp++ = 0; *bp++ = 0; } size = bp - buf; be16enc(buf, size - 2); if (len > size) len = size; write_prdt(p, slot, cfis, buf, len); cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); break; } default: { uint32_t tfd; p->sense_key = ATA_SENSE_ILLEGAL_REQUEST; p->asc = 0x24; tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR; cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, tfd); break; } } } static void atapi_report_luns(struct ahci_port *p, int slot, uint8_t *cfis) { uint8_t buf[16]; memset(buf, 0, sizeof(buf)); buf[3] = 8; cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; write_prdt(p, slot, cfis, buf, sizeof(buf)); ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); } static void atapi_read(struct ahci_port *p, int slot, uint8_t *cfis, uint32_t done) { struct ahci_ioreq *aior; struct ahci_cmd_hdr *hdr; struct ahci_prdt_entry *prdt; struct blockif_req *breq; uint8_t *acmd; uint64_t lba; uint32_t len; int err; acmd = cfis + 0x40; hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE); prdt = (struct ahci_prdt_entry *)(cfis + 0x80); lba = be32dec(acmd + 2); if (acmd[0] == READ_10) len = be16dec(acmd + 7); else len = be32dec(acmd + 6); if (len == 0) { cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); } lba *= 2048; len *= 2048; /* * Pull request off free list */ aior = STAILQ_FIRST(&p->iofhd); assert(aior != NULL); STAILQ_REMOVE_HEAD(&p->iofhd, io_flist); aior->cfis = cfis; aior->slot = slot; aior->len = len; aior->done = done; breq = &aior->io_req; breq->br_offset = lba + done; ahci_build_iov(p, aior, prdt, hdr->prdtl); /* Mark this command in-flight. */ p->pending |= 1 << slot; /* Stuff request onto busy list. */ TAILQ_INSERT_HEAD(&p->iobhd, aior, io_blist); err = blockif_read(p->bctx, breq); assert(err == 0); } static void atapi_request_sense(struct ahci_port *p, int slot, uint8_t *cfis) { uint8_t buf[64]; uint8_t *acmd; int len; acmd = cfis + 0x40; len = acmd[4]; if (len > sizeof(buf)) len = sizeof(buf); memset(buf, 0, len); buf[0] = 0x70 | (1 << 7); buf[2] = p->sense_key; buf[7] = 10; buf[12] = p->asc; write_prdt(p, slot, cfis, buf, len); cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); } static void atapi_start_stop_unit(struct ahci_port *p, int slot, uint8_t *cfis) { uint8_t *acmd = cfis + 0x40; uint32_t tfd; switch (acmd[4] & 3) { case 0: case 1: case 3: cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; tfd = ATA_S_READY | ATA_S_DSC; break; case 2: /* TODO eject media */ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; p->sense_key = ATA_SENSE_ILLEGAL_REQUEST; p->asc = 0x53; tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR; break; } ahci_write_fis_d2h(p, slot, cfis, tfd); } static void atapi_mode_sense(struct ahci_port *p, int slot, uint8_t *cfis) { uint8_t *acmd; uint32_t tfd; uint8_t pc, code; int len; acmd = cfis + 0x40; len = be16dec(acmd + 7); pc = acmd[2] >> 6; code = acmd[2] & 0x3f; switch (pc) { case 0: switch (code) { case MODEPAGE_RW_ERROR_RECOVERY: { uint8_t buf[16]; if (len > sizeof(buf)) len = sizeof(buf); memset(buf, 0, sizeof(buf)); be16enc(buf, 16 - 2); buf[2] = 0x70; buf[8] = 0x01; buf[9] = 16 - 10; buf[11] = 0x05; write_prdt(p, slot, cfis, buf, len); tfd = ATA_S_READY | ATA_S_DSC; break; } case MODEPAGE_CD_CAPABILITIES: { uint8_t buf[30]; if (len > sizeof(buf)) len = sizeof(buf); memset(buf, 0, sizeof(buf)); be16enc(buf, 30 - 2); buf[2] = 0x70; buf[8] = 0x2A; buf[9] = 30 - 10; buf[10] = 0x08; buf[12] = 0x71; be16enc(&buf[18], 2); be16enc(&buf[20], 512); write_prdt(p, slot, cfis, buf, len); tfd = ATA_S_READY | ATA_S_DSC; break; } default: goto error; break; } break; case 3: p->sense_key = ATA_SENSE_ILLEGAL_REQUEST; p->asc = 0x39; tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR; break; error: case 1: case 2: p->sense_key = ATA_SENSE_ILLEGAL_REQUEST; p->asc = 0x24; tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR; break; } cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, tfd); } static void atapi_get_event_status_notification(struct ahci_port *p, int slot, uint8_t *cfis) { uint8_t *acmd; uint32_t tfd; acmd = cfis + 0x40; /* we don't support asynchronous operation */ if (!(acmd[1] & 1)) { p->sense_key = ATA_SENSE_ILLEGAL_REQUEST; p->asc = 0x24; tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR; } else { uint8_t buf[8]; int len; len = be16dec(acmd + 7); if (len > sizeof(buf)) len = sizeof(buf); memset(buf, 0, sizeof(buf)); be16enc(buf, 8 - 2); buf[2] = 0x04; buf[3] = 0x10; buf[5] = 0x02; write_prdt(p, slot, cfis, buf, len); tfd = ATA_S_READY | ATA_S_DSC; } cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, tfd); } static void handle_packet_cmd(struct ahci_port *p, int slot, uint8_t *cfis) { uint8_t *acmd; acmd = cfis + 0x40; #ifdef AHCI_DEBUG { int i; DPRINTF("ACMD:"); for (i = 0; i < 16; i++) DPRINTF("%02x ", acmd[i]); DPRINTF("\n"); } #endif switch (acmd[0]) { case TEST_UNIT_READY: cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); break; case INQUIRY: atapi_inquiry(p, slot, cfis); break; case READ_CAPACITY: atapi_read_capacity(p, slot, cfis); break; case PREVENT_ALLOW: /* TODO */ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); break; case READ_TOC: atapi_read_toc(p, slot, cfis); break; case REPORT_LUNS: atapi_report_luns(p, slot, cfis); break; case READ_10: case READ_12: atapi_read(p, slot, cfis, 0); break; case REQUEST_SENSE: atapi_request_sense(p, slot, cfis); break; case START_STOP_UNIT: atapi_start_stop_unit(p, slot, cfis); break; case MODE_SENSE_10: atapi_mode_sense(p, slot, cfis); break; case GET_EVENT_STATUS_NOTIFICATION: atapi_get_event_status_notification(p, slot, cfis); break; default: cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; p->sense_key = ATA_SENSE_ILLEGAL_REQUEST; p->asc = 0x20; ahci_write_fis_d2h(p, slot, cfis, (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR); break; } } static void ahci_handle_cmd(struct ahci_port *p, int slot, uint8_t *cfis) { p->tfd |= ATA_S_BUSY; switch (cfis[2]) { case ATA_ATA_IDENTIFY: handle_identify(p, slot, cfis); break; case ATA_SETFEATURES: { switch (cfis[3]) { case ATA_SF_ENAB_SATA_SF: switch (cfis[12]) { case ATA_SATA_SF_AN: p->tfd = ATA_S_DSC | ATA_S_READY; break; default: p->tfd = ATA_S_ERROR | ATA_S_READY; p->tfd |= (ATA_ERROR_ABORT << 8); break; } break; case ATA_SF_ENAB_WCACHE: case ATA_SF_DIS_WCACHE: case ATA_SF_ENAB_RCACHE: case ATA_SF_DIS_RCACHE: p->tfd = ATA_S_DSC | ATA_S_READY; break; case ATA_SF_SETXFER: { switch (cfis[12] & 0xf8) { case ATA_PIO: case ATA_PIO0: break; case ATA_WDMA0: case ATA_UDMA0: p->xfermode = (cfis[12] & 0x7); break; } p->tfd = ATA_S_DSC | ATA_S_READY; break; } default: p->tfd = ATA_S_ERROR | ATA_S_READY; p->tfd |= (ATA_ERROR_ABORT << 8); break; } ahci_write_fis_d2h(p, slot, cfis, p->tfd); break; } case ATA_SET_MULTI: if (cfis[12] != 0 && (cfis[12] > 128 || (cfis[12] & (cfis[12] - 1)))) { p->tfd = ATA_S_ERROR | ATA_S_READY; p->tfd |= (ATA_ERROR_ABORT << 8); } else { p->mult_sectors = cfis[12]; p->tfd = ATA_S_DSC | ATA_S_READY; } ahci_write_fis_d2h(p, slot, cfis, p->tfd); break; case ATA_READ: case ATA_WRITE: case ATA_READ48: case ATA_WRITE48: case ATA_READ_MUL: case ATA_WRITE_MUL: case ATA_READ_MUL48: case ATA_WRITE_MUL48: case ATA_READ_DMA: case ATA_WRITE_DMA: case ATA_READ_DMA48: case ATA_WRITE_DMA48: case ATA_READ_FPDMA_QUEUED: case ATA_WRITE_FPDMA_QUEUED: ahci_handle_rw(p, slot, cfis, 0); break; case ATA_FLUSHCACHE: case ATA_FLUSHCACHE48: ahci_handle_flush(p, slot, cfis); break; case ATA_DATA_SET_MANAGEMENT: if (cfis[11] == 0 && cfis[3] == ATA_DSM_TRIM && cfis[13] == 0 && cfis[12] == 1) { ahci_handle_dsm_trim(p, slot, cfis, 0); break; } ahci_write_fis_d2h(p, slot, cfis, (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR); break; case ATA_SEND_FPDMA_QUEUED: if ((cfis[13] & 0x1f) == ATA_SFPDMA_DSM && cfis[17] == 0 && cfis[16] == ATA_DSM_TRIM && cfis[11] == 0 && cfis[13] == 1) { ahci_handle_dsm_trim(p, slot, cfis, 0); break; } ahci_write_fis_d2h(p, slot, cfis, (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR); break; case ATA_READ_LOG_EXT: case ATA_READ_LOG_DMA_EXT: ahci_handle_read_log(p, slot, cfis); break; case ATA_SECURITY_FREEZE_LOCK: case ATA_SMART_CMD: case ATA_NOP: ahci_write_fis_d2h(p, slot, cfis, (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR); break; case ATA_CHECK_POWER_MODE: cfis[12] = 0xff; /* always on */ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); break; case ATA_STANDBY_CMD: case ATA_STANDBY_IMMEDIATE: case ATA_IDLE_CMD: case ATA_IDLE_IMMEDIATE: case ATA_SLEEP: case ATA_READ_VERIFY: case ATA_READ_VERIFY48: ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC); break; case ATA_ATAPI_IDENTIFY: handle_atapi_identify(p, slot, cfis); break; case ATA_PACKET_CMD: if (!p->atapi) { ahci_write_fis_d2h(p, slot, cfis, (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR); } else handle_packet_cmd(p, slot, cfis); break; default: WPRINTF("Unsupported cmd:%02x\n", cfis[2]); ahci_write_fis_d2h(p, slot, cfis, (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR); break; } } static void ahci_handle_slot(struct ahci_port *p, int slot) { struct ahci_cmd_hdr *hdr; struct ahci_prdt_entry *prdt; struct pci_ahci_softc *sc; uint8_t *cfis; int cfl; sc = p->pr_sc; hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE); cfl = (hdr->flags & 0x1f) * 4; cfis = paddr_guest2host(ahci_ctx(sc), hdr->ctba, 0x80 + hdr->prdtl * sizeof(struct ahci_prdt_entry)); prdt = (struct ahci_prdt_entry *)(cfis + 0x80); #ifdef AHCI_DEBUG DPRINTF("\ncfis:"); for (i = 0; i < cfl; i++) { if (i % 10 == 0) DPRINTF("\n"); DPRINTF("%02x ", cfis[i]); } DPRINTF("\n"); for (i = 0; i < hdr->prdtl; i++) { DPRINTF("%d@%08"PRIx64"\n", prdt->dbc & 0x3fffff, prdt->dba); prdt++; } #endif if (cfis[0] != FIS_TYPE_REGH2D) { WPRINTF("Not a H2D FIS:%02x\n", cfis[0]); return; } if (cfis[1] & 0x80) { ahci_handle_cmd(p, slot, cfis); } else { if (cfis[15] & (1 << 2)) p->reset = 1; else if (p->reset) { p->reset = 0; ahci_port_reset(p); } p->ci &= ~(1 << slot); } } static void ahci_handle_port(struct ahci_port *p) { if (!(p->cmd & AHCI_P_CMD_ST)) return; /* * Search for any new commands to issue ignoring those that * are already in-flight. Stop if device is busy or in error. */ for (; (p->ci & ~p->pending) != 0; p->ccs = ((p->ccs + 1) & 31)) { if ((p->tfd & (ATA_S_BUSY | ATA_S_DRQ)) != 0) break; if (p->waitforclear) break; if ((p->ci & ~p->pending & (1 << p->ccs)) != 0) { p->cmd &= ~AHCI_P_CMD_CCS_MASK; p->cmd |= p->ccs << AHCI_P_CMD_CCS_SHIFT; ahci_handle_slot(p, p->ccs); } } } /* * blockif callback routine - this runs in the context of the blockif * i/o thread, so the mutex needs to be acquired. */ static void ata_ioreq_cb(struct blockif_req *br, int err) { struct ahci_cmd_hdr *hdr; struct ahci_ioreq *aior; struct ahci_port *p; struct pci_ahci_softc *sc; uint32_t tfd; uint8_t *cfis; int slot, ncq, dsm; DPRINTF("%s %d\n", __func__, err); ncq = dsm = 0; aior = br->br_param; p = aior->io_pr; cfis = aior->cfis; slot = aior->slot; sc = p->pr_sc; hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE); if (cfis[2] == ATA_WRITE_FPDMA_QUEUED || cfis[2] == ATA_READ_FPDMA_QUEUED || cfis[2] == ATA_SEND_FPDMA_QUEUED) ncq = 1; if (cfis[2] == ATA_DATA_SET_MANAGEMENT || (cfis[2] == ATA_SEND_FPDMA_QUEUED && (cfis[13] & 0x1f) == ATA_SFPDMA_DSM)) dsm = 1; pthread_mutex_lock(&sc->mtx); /* * Delete the blockif request from the busy list */ TAILQ_REMOVE(&p->iobhd, aior, io_blist); /* * Move the blockif request back to the free list */ STAILQ_INSERT_TAIL(&p->iofhd, aior, io_flist); if (!err) hdr->prdbc = aior->done; if (!err && aior->more) { if (dsm) ahci_handle_dsm_trim(p, slot, cfis, aior->done); else ahci_handle_rw(p, slot, cfis, aior->done); goto out; } if (!err) tfd = ATA_S_READY | ATA_S_DSC; else tfd = (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR; if (ncq) ahci_write_fis_sdb(p, slot, cfis, tfd); else ahci_write_fis_d2h(p, slot, cfis, tfd); /* * This command is now complete. */ p->pending &= ~(1 << slot); ahci_check_stopped(p); ahci_handle_port(p); out: pthread_mutex_unlock(&sc->mtx); DPRINTF("%s exit\n", __func__); } static void atapi_ioreq_cb(struct blockif_req *br, int err) { struct ahci_cmd_hdr *hdr; struct ahci_ioreq *aior; struct ahci_port *p; struct pci_ahci_softc *sc; uint8_t *cfis; uint32_t tfd; int slot; DPRINTF("%s %d\n", __func__, err); aior = br->br_param; p = aior->io_pr; cfis = aior->cfis; slot = aior->slot; sc = p->pr_sc; hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + aior->slot * AHCI_CL_SIZE); pthread_mutex_lock(&sc->mtx); /* * Delete the blockif request from the busy list */ TAILQ_REMOVE(&p->iobhd, aior, io_blist); /* * Move the blockif request back to the free list */ STAILQ_INSERT_TAIL(&p->iofhd, aior, io_flist); if (!err) hdr->prdbc = aior->done; if (!err && aior->more) { atapi_read(p, slot, cfis, aior->done); goto out; } if (!err) { tfd = ATA_S_READY | ATA_S_DSC; } else { p->sense_key = ATA_SENSE_ILLEGAL_REQUEST; p->asc = 0x21; tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR; } cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN; ahci_write_fis_d2h(p, slot, cfis, tfd); /* * This command is now complete. */ p->pending &= ~(1 << slot); ahci_check_stopped(p); ahci_handle_port(p); out: pthread_mutex_unlock(&sc->mtx); DPRINTF("%s exit\n", __func__); } static void pci_ahci_ioreq_init(struct ahci_port *pr) { struct ahci_ioreq *vr; int i; pr->ioqsz = blockif_queuesz(pr->bctx); pr->ioreq = calloc(pr->ioqsz, sizeof(struct ahci_ioreq)); STAILQ_INIT(&pr->iofhd); /* * Add all i/o request entries to the free queue */ for (i = 0; i < pr->ioqsz; i++) { vr = &pr->ioreq[i]; vr->io_pr = pr; if (!pr->atapi) vr->io_req.br_callback = ata_ioreq_cb; else vr->io_req.br_callback = atapi_ioreq_cb; vr->io_req.br_param = vr; STAILQ_INSERT_TAIL(&pr->iofhd, vr, io_flist); } TAILQ_INIT(&pr->iobhd); } static void pci_ahci_port_write(struct pci_ahci_softc *sc, uint64_t offset, uint64_t value) { int port = (offset - AHCI_OFFSET) / AHCI_STEP; offset = (offset - AHCI_OFFSET) % AHCI_STEP; struct ahci_port *p = &sc->port[port]; DPRINTF("pci_ahci_port %d: write offset 0x%"PRIx64" value 0x%"PRIx64"\n", port, offset, value); switch (offset) { case AHCI_P_CLB: p->clb = value; break; case AHCI_P_CLBU: p->clbu = value; break; case AHCI_P_FB: p->fb = value; break; case AHCI_P_FBU: p->fbu = value; break; case AHCI_P_IS: p->is &= ~value; break; case AHCI_P_IE: p->ie = value & 0xFDC000FF; ahci_generate_intr(sc); break; case AHCI_P_CMD: { p->cmd &= ~(AHCI_P_CMD_ST | AHCI_P_CMD_SUD | AHCI_P_CMD_POD | AHCI_P_CMD_CLO | AHCI_P_CMD_FRE | AHCI_P_CMD_APSTE | AHCI_P_CMD_ATAPI | AHCI_P_CMD_DLAE | AHCI_P_CMD_ALPE | AHCI_P_CMD_ASP | AHCI_P_CMD_ICC_MASK); p->cmd |= (AHCI_P_CMD_ST | AHCI_P_CMD_SUD | AHCI_P_CMD_POD | AHCI_P_CMD_CLO | AHCI_P_CMD_FRE | AHCI_P_CMD_APSTE | AHCI_P_CMD_ATAPI | AHCI_P_CMD_DLAE | AHCI_P_CMD_ALPE | AHCI_P_CMD_ASP | AHCI_P_CMD_ICC_MASK) & value; if (!(value & AHCI_P_CMD_ST)) { ahci_port_stop(p); } else { uint64_t clb; p->cmd |= AHCI_P_CMD_CR; clb = (uint64_t)p->clbu << 32 | p->clb; p->cmd_lst = paddr_guest2host(ahci_ctx(sc), clb, AHCI_CL_SIZE * AHCI_MAX_SLOTS); } if (value & AHCI_P_CMD_FRE) { uint64_t fb; p->cmd |= AHCI_P_CMD_FR; fb = (uint64_t)p->fbu << 32 | p->fb; /* we don't support FBSCP, so rfis size is 256Bytes */ p->rfis = paddr_guest2host(ahci_ctx(sc), fb, 256); } else { p->cmd &= ~AHCI_P_CMD_FR; } if (value & AHCI_P_CMD_CLO) { p->tfd &= ~(ATA_S_BUSY | ATA_S_DRQ); p->cmd &= ~AHCI_P_CMD_CLO; } if (value & AHCI_P_CMD_ICC_MASK) { p->cmd &= ~AHCI_P_CMD_ICC_MASK; } ahci_handle_port(p); break; } case AHCI_P_TFD: case AHCI_P_SIG: case AHCI_P_SSTS: WPRINTF("pci_ahci_port: read only registers 0x%"PRIx64"\n", offset); break; case AHCI_P_SCTL: p->sctl = value; if (!(p->cmd & AHCI_P_CMD_ST)) { if (value & ATA_SC_DET_RESET) ahci_port_reset(p); } break; case AHCI_P_SERR: p->serr &= ~value; break; case AHCI_P_SACT: p->sact |= value; break; case AHCI_P_CI: p->ci |= value; ahci_handle_port(p); break; case AHCI_P_SNTF: case AHCI_P_FBS: default: break; } } static void pci_ahci_host_write(struct pci_ahci_softc *sc, uint64_t offset, uint64_t value) { DPRINTF("pci_ahci_host: write offset 0x%"PRIx64" value 0x%"PRIx64"\n", offset, value); switch (offset) { case AHCI_CAP: case AHCI_PI: case AHCI_VS: case AHCI_CAP2: DPRINTF("pci_ahci_host: read only registers 0x%"PRIx64"\n", offset); break; case AHCI_GHC: if (value & AHCI_GHC_HR) ahci_reset(sc); else if (value & AHCI_GHC_IE) { sc->ghc |= AHCI_GHC_IE; ahci_generate_intr(sc); } break; case AHCI_IS: sc->is &= ~value; ahci_generate_intr(sc); break; default: break; } } static void pci_ahci_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, uint64_t offset, int size, uint64_t value) { struct pci_ahci_softc *sc = pi->pi_arg; assert(baridx == 5); assert((offset % 4) == 0 && size == 4); pthread_mutex_lock(&sc->mtx); if (offset < AHCI_OFFSET) pci_ahci_host_write(sc, offset, value); else if (offset < AHCI_OFFSET + sc->ports * AHCI_STEP) pci_ahci_port_write(sc, offset, value); else WPRINTF("pci_ahci: unknown i/o write offset 0x%"PRIx64"\n", offset); pthread_mutex_unlock(&sc->mtx); } static uint64_t pci_ahci_host_read(struct pci_ahci_softc *sc, uint64_t offset) { uint32_t value; switch (offset) { case AHCI_CAP: case AHCI_GHC: case AHCI_IS: case AHCI_PI: case AHCI_VS: case AHCI_CCCC: case AHCI_CCCP: case AHCI_EM_LOC: case AHCI_EM_CTL: case AHCI_CAP2: { uint32_t *p = &sc->cap; p += (offset - AHCI_CAP) / sizeof(uint32_t); value = *p; break; } default: value = 0; break; } DPRINTF("pci_ahci_host: read offset 0x%"PRIx64" value 0x%x\n", offset, value); return (value); } static uint64_t pci_ahci_port_read(struct pci_ahci_softc *sc, uint64_t offset) { uint32_t value; int port = (offset - AHCI_OFFSET) / AHCI_STEP; offset = (offset - AHCI_OFFSET) % AHCI_STEP; switch (offset) { case AHCI_P_CLB: case AHCI_P_CLBU: case AHCI_P_FB: case AHCI_P_FBU: case AHCI_P_IS: case AHCI_P_IE: case AHCI_P_CMD: case AHCI_P_TFD: case AHCI_P_SIG: case AHCI_P_SSTS: case AHCI_P_SCTL: case AHCI_P_SERR: case AHCI_P_SACT: case AHCI_P_CI: case AHCI_P_SNTF: case AHCI_P_FBS: { uint32_t *p= &sc->port[port].clb; p += (offset - AHCI_P_CLB) / sizeof(uint32_t); value = *p; break; } default: value = 0; break; } DPRINTF("pci_ahci_port %d: read offset 0x%"PRIx64" value 0x%x\n", port, offset, value); return value; } static uint64_t pci_ahci_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, uint64_t regoff, int size) { struct pci_ahci_softc *sc = pi->pi_arg; uint64_t offset; uint32_t value; assert(baridx == 5); assert(size == 1 || size == 2 || size == 4); assert((regoff & (size - 1)) == 0); pthread_mutex_lock(&sc->mtx); offset = regoff & ~0x3; /* round down to a multiple of 4 bytes */ if (offset < AHCI_OFFSET) value = pci_ahci_host_read(sc, offset); else if (offset < AHCI_OFFSET + sc->ports * AHCI_STEP) value = pci_ahci_port_read(sc, offset); else { value = 0; WPRINTF("pci_ahci: unknown i/o read offset 0x%"PRIx64"\n", regoff); } value >>= 8 * (regoff & 0x3); pthread_mutex_unlock(&sc->mtx); return (value); } static int pci_ahci_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts, int atapi) { char bident[sizeof("XX:X:X")]; struct blockif_ctxt *bctxt; struct pci_ahci_softc *sc; int ret, slots; MD5_CTX mdctx; u_char digest[16]; ret = 0; if (opts == NULL) { fprintf(stderr, "pci_ahci: backing device required\n"); return (1); } #ifdef AHCI_DEBUG dbg = fopen("/tmp/log", "w+"); #endif sc = calloc(1, sizeof(struct pci_ahci_softc)); pi->pi_arg = sc; sc->asc_pi = pi; sc->ports = MAX_PORTS; /* * Only use port 0 for a backing device. All other ports will be * marked as unused */ sc->port[0].atapi = atapi; /* * Attempt to open the backing image. Use the PCI * slot/func for the identifier string. */ snprintf(bident, sizeof(bident), "%d:%d", pi->pi_slot, pi->pi_func); bctxt = blockif_open(opts, bident); if (bctxt == NULL) { ret = 1; goto open_fail; } sc->port[0].bctx = bctxt; sc->port[0].pr_sc = sc; /* * Create an identifier for the backing file. Use parts of the * md5 sum of the filename */ MD5Init(&mdctx); MD5Update(&mdctx, opts, strlen(opts)); MD5Final(digest, &mdctx); sprintf(sc->port[0].ident, "BHYVE-%02X%02X-%02X%02X-%02X%02X", digest[0], digest[1], digest[2], digest[3], digest[4], digest[5]); /* * Allocate blockif request structures and add them * to the free list */ pci_ahci_ioreq_init(&sc->port[0]); pthread_mutex_init(&sc->mtx, NULL); /* Intel ICH8 AHCI */ slots = sc->port[0].ioqsz; if (slots > 32) slots = 32; --slots; sc->cap = AHCI_CAP_64BIT | AHCI_CAP_SNCQ | AHCI_CAP_SSNTF | AHCI_CAP_SMPS | AHCI_CAP_SSS | AHCI_CAP_SALP | AHCI_CAP_SAL | AHCI_CAP_SCLO | (0x3 << AHCI_CAP_ISS_SHIFT)| AHCI_CAP_PMD | AHCI_CAP_SSC | AHCI_CAP_PSC | (slots << AHCI_CAP_NCS_SHIFT) | AHCI_CAP_SXS | (sc->ports - 1); /* Only port 0 implemented */ sc->pi = 1; sc->vs = 0x10300; sc->cap2 = AHCI_CAP2_APST; ahci_reset(sc); pci_set_cfgdata16(pi, PCIR_DEVICE, 0x2821); pci_set_cfgdata16(pi, PCIR_VENDOR, 0x8086); pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_STORAGE); pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_STORAGE_SATA); pci_set_cfgdata8(pi, PCIR_PROGIF, PCIP_STORAGE_SATA_AHCI_1_0); pci_emul_add_msicap(pi, 1); pci_emul_alloc_bar(pi, 5, PCIBAR_MEM32, AHCI_OFFSET + sc->ports * AHCI_STEP); pci_lintr_request(pi); open_fail: if (ret) { if (sc->port[0].bctx != NULL) blockif_close(sc->port[0].bctx); free(sc); } return (ret); } static int pci_ahci_hd_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) { return (pci_ahci_init(ctx, pi, opts, 0)); } static int pci_ahci_atapi_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) { return (pci_ahci_init(ctx, pi, opts, 1)); } /* * Use separate emulation names to distinguish drive and atapi devices */ struct pci_devemu pci_de_ahci_hd = { .pe_emu = "ahci-hd", .pe_init = pci_ahci_hd_init, .pe_barwrite = pci_ahci_write, .pe_barread = pci_ahci_read }; PCI_EMUL_SET(pci_de_ahci_hd); struct pci_devemu pci_de_ahci_cd = { .pe_emu = "ahci-cd", .pe_init = pci_ahci_atapi_init, .pe_barwrite = pci_ahci_write, .pe_barread = pci_ahci_read }; PCI_EMUL_SET(pci_de_ahci_cd); Index: projects/release-pkg/usr.sbin/bhyve =================================================================== --- projects/release-pkg/usr.sbin/bhyve (revision 297604) +++ projects/release-pkg/usr.sbin/bhyve (revision 297605) Property changes on: projects/release-pkg/usr.sbin/bhyve ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/usr.sbin/bhyve:r297567-297604 Index: projects/release-pkg/usr.sbin/bhyveload/bhyveload.c =================================================================== --- projects/release-pkg/usr.sbin/bhyveload/bhyveload.c (revision 297604) +++ projects/release-pkg/usr.sbin/bhyveload/bhyveload.c (revision 297605) @@ -1,794 +1,793 @@ /*- * Copyright (c) 2011 NetApp, Inc. * 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 NETAPP, INC ``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 NETAPP, INC 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$ */ /*- * Copyright (c) 2011 Google, Inc. * 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$ */ #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 "userboot.h" #define MB (1024 * 1024UL) #define GB (1024 * 1024 * 1024UL) #define BSP 0 #define NDISKS 32 static char *host_base; static struct termios term, oldterm; static int disk_fd[NDISKS]; static int ndisks; static int consin_fd, consout_fd; static char *vmname, *progname; static struct vmctx *ctx; static uint64_t gdtbase, cr3, rsp; static void cb_exit(void *arg, int v); /* * Console i/o callbacks */ static void cb_putc(void *arg, int ch) { char c = ch; (void) write(consout_fd, &c, 1); } static int cb_getc(void *arg) { char c; if (read(consin_fd, &c, 1) == 1) return (c); return (-1); } static int cb_poll(void *arg) { int n; if (ioctl(consin_fd, FIONREAD, &n) >= 0) return (n > 0); return (0); } /* * Host filesystem i/o callbacks */ struct cb_file { int cf_isdir; size_t cf_size; struct stat cf_stat; union { int fd; DIR *dir; } cf_u; }; static int cb_open(void *arg, const char *filename, void **hp) { - struct stat st; struct cb_file *cf; char path[PATH_MAX]; if (!host_base) return (ENOENT); strlcpy(path, host_base, PATH_MAX); if (path[strlen(path) - 1] == '/') path[strlen(path) - 1] = 0; strlcat(path, filename, PATH_MAX); cf = malloc(sizeof(struct cb_file)); if (stat(path, &cf->cf_stat) < 0) { free(cf); return (errno); } - cf->cf_size = st.st_size; + cf->cf_size = cf->cf_stat.st_size; if (S_ISDIR(cf->cf_stat.st_mode)) { cf->cf_isdir = 1; cf->cf_u.dir = opendir(path); if (!cf->cf_u.dir) goto out; *hp = cf; return (0); } if (S_ISREG(cf->cf_stat.st_mode)) { cf->cf_isdir = 0; cf->cf_u.fd = open(path, O_RDONLY); if (cf->cf_u.fd < 0) goto out; *hp = cf; return (0); } out: free(cf); return (EINVAL); } static int cb_close(void *arg, void *h) { struct cb_file *cf = h; if (cf->cf_isdir) closedir(cf->cf_u.dir); else close(cf->cf_u.fd); free(cf); return (0); } static int cb_isdir(void *arg, void *h) { struct cb_file *cf = h; return (cf->cf_isdir); } static int cb_read(void *arg, void *h, void *buf, size_t size, size_t *resid) { struct cb_file *cf = h; ssize_t sz; if (cf->cf_isdir) return (EINVAL); sz = read(cf->cf_u.fd, buf, size); if (sz < 0) return (EINVAL); *resid = size - sz; return (0); } static int cb_readdir(void *arg, void *h, uint32_t *fileno_return, uint8_t *type_return, size_t *namelen_return, char *name) { struct cb_file *cf = h; struct dirent *dp; if (!cf->cf_isdir) return (EINVAL); dp = readdir(cf->cf_u.dir); if (!dp) return (ENOENT); /* * Note: d_namlen is in the range 0..255 and therefore less * than PATH_MAX so we don't need to test before copying. */ *fileno_return = dp->d_fileno; *type_return = dp->d_type; *namelen_return = dp->d_namlen; memcpy(name, dp->d_name, dp->d_namlen); name[dp->d_namlen] = 0; return (0); } static int cb_seek(void *arg, void *h, uint64_t offset, int whence) { struct cb_file *cf = h; if (cf->cf_isdir) return (EINVAL); if (lseek(cf->cf_u.fd, offset, whence) < 0) return (errno); return (0); } static int cb_stat(void *arg, void *h, int *mode, int *uid, int *gid, uint64_t *size) { struct cb_file *cf = h; *mode = cf->cf_stat.st_mode; *uid = cf->cf_stat.st_uid; *gid = cf->cf_stat.st_gid; *size = cf->cf_stat.st_size; return (0); } /* * Disk image i/o callbacks */ static int cb_diskread(void *arg, int unit, uint64_t from, void *to, size_t size, size_t *resid) { ssize_t n; if (unit < 0 || unit >= ndisks ) return (EIO); n = pread(disk_fd[unit], to, size, from); if (n < 0) return (errno); *resid = size - n; return (0); } static int cb_diskioctl(void *arg, int unit, u_long cmd, void *data) { struct stat sb; if (unit < 0 || unit >= ndisks) return (EBADF); switch (cmd) { case DIOCGSECTORSIZE: *(u_int *)data = 512; break; case DIOCGMEDIASIZE: if (fstat(disk_fd[unit], &sb) == 0) *(off_t *)data = sb.st_size; else return (ENOTTY); break; default: return (ENOTTY); } return (0); } /* * Guest virtual machine i/o callbacks */ static int cb_copyin(void *arg, const void *from, uint64_t to, size_t size) { char *ptr; to &= 0x7fffffff; ptr = vm_map_gpa(ctx, to, size); if (ptr == NULL) return (EFAULT); memcpy(ptr, from, size); return (0); } static int cb_copyout(void *arg, uint64_t from, void *to, size_t size) { char *ptr; from &= 0x7fffffff; ptr = vm_map_gpa(ctx, from, size); if (ptr == NULL) return (EFAULT); memcpy(to, ptr, size); return (0); } static void cb_setreg(void *arg, int r, uint64_t v) { int error; enum vm_reg_name vmreg; vmreg = VM_REG_LAST; switch (r) { case 4: vmreg = VM_REG_GUEST_RSP; rsp = v; break; default: break; } if (vmreg == VM_REG_LAST) { printf("test_setreg(%d): not implemented\n", r); cb_exit(NULL, USERBOOT_EXIT_QUIT); } error = vm_set_register(ctx, BSP, vmreg, v); if (error) { perror("vm_set_register"); cb_exit(NULL, USERBOOT_EXIT_QUIT); } } static void cb_setmsr(void *arg, int r, uint64_t v) { int error; enum vm_reg_name vmreg; vmreg = VM_REG_LAST; switch (r) { case MSR_EFER: vmreg = VM_REG_GUEST_EFER; break; default: break; } if (vmreg == VM_REG_LAST) { printf("test_setmsr(%d): not implemented\n", r); cb_exit(NULL, USERBOOT_EXIT_QUIT); } error = vm_set_register(ctx, BSP, vmreg, v); if (error) { perror("vm_set_msr"); cb_exit(NULL, USERBOOT_EXIT_QUIT); } } static void cb_setcr(void *arg, int r, uint64_t v) { int error; enum vm_reg_name vmreg; vmreg = VM_REG_LAST; switch (r) { case 0: vmreg = VM_REG_GUEST_CR0; break; case 3: vmreg = VM_REG_GUEST_CR3; cr3 = v; break; case 4: vmreg = VM_REG_GUEST_CR4; break; default: break; } if (vmreg == VM_REG_LAST) { printf("test_setcr(%d): not implemented\n", r); cb_exit(NULL, USERBOOT_EXIT_QUIT); } error = vm_set_register(ctx, BSP, vmreg, v); if (error) { perror("vm_set_cr"); cb_exit(NULL, USERBOOT_EXIT_QUIT); } } static void cb_setgdt(void *arg, uint64_t base, size_t size) { int error; error = vm_set_desc(ctx, BSP, VM_REG_GUEST_GDTR, base, size - 1, 0); if (error != 0) { perror("vm_set_desc(gdt)"); cb_exit(NULL, USERBOOT_EXIT_QUIT); } gdtbase = base; } static void cb_exec(void *arg, uint64_t rip) { int error; if (cr3 == 0) error = vm_setup_freebsd_registers_i386(ctx, BSP, rip, gdtbase, rsp); else error = vm_setup_freebsd_registers(ctx, BSP, rip, cr3, gdtbase, rsp); if (error) { perror("vm_setup_freebsd_registers"); cb_exit(NULL, USERBOOT_EXIT_QUIT); } cb_exit(NULL, 0); } /* * Misc */ static void cb_delay(void *arg, int usec) { usleep(usec); } static void cb_exit(void *arg, int v) { tcsetattr(consout_fd, TCSAFLUSH, &oldterm); exit(v); } static void cb_getmem(void *arg, uint64_t *ret_lowmem, uint64_t *ret_highmem) { *ret_lowmem = vm_get_lowmem_size(ctx); *ret_highmem = vm_get_highmem_size(ctx); } struct env { const char *str; /* name=value */ SLIST_ENTRY(env) next; }; static SLIST_HEAD(envhead, env) envhead; static void addenv(const char *str) { struct env *env; env = malloc(sizeof(struct env)); env->str = str; SLIST_INSERT_HEAD(&envhead, env, next); } static const char * cb_getenv(void *arg, int num) { int i; struct env *env; i = 0; SLIST_FOREACH(env, &envhead, next) { if (i == num) return (env->str); i++; } return (NULL); } static int cb_vm_set_register(void *arg, int vcpu, int reg, uint64_t val) { return (vm_set_register(ctx, vcpu, reg, val)); } static int cb_vm_set_desc(void *arg, int vcpu, int reg, uint64_t base, u_int limit, u_int access) { return (vm_set_desc(ctx, vcpu, reg, base, limit, access)); } static struct loader_callbacks cb = { .getc = cb_getc, .putc = cb_putc, .poll = cb_poll, .open = cb_open, .close = cb_close, .isdir = cb_isdir, .read = cb_read, .readdir = cb_readdir, .seek = cb_seek, .stat = cb_stat, .diskread = cb_diskread, .diskioctl = cb_diskioctl, .copyin = cb_copyin, .copyout = cb_copyout, .setreg = cb_setreg, .setmsr = cb_setmsr, .setcr = cb_setcr, .setgdt = cb_setgdt, .exec = cb_exec, .delay = cb_delay, .exit = cb_exit, .getmem = cb_getmem, .getenv = cb_getenv, /* Version 4 additions */ .vm_set_register = cb_vm_set_register, .vm_set_desc = cb_vm_set_desc, }; static int altcons_open(char *path) { struct stat sb; int err; int fd; /* * Allow stdio to be passed in so that the same string * can be used for the bhyveload console and bhyve com-port * parameters */ if (!strcmp(path, "stdio")) return (0); err = stat(path, &sb); if (err == 0) { if (!S_ISCHR(sb.st_mode)) err = ENOTSUP; else { fd = open(path, O_RDWR | O_NONBLOCK); if (fd < 0) err = errno; else consin_fd = consout_fd = fd; } } return (err); } static int disk_open(char *path) { int err, fd; if (ndisks >= NDISKS) return (ERANGE); err = 0; fd = open(path, O_RDONLY); if (fd > 0) { disk_fd[ndisks] = fd; ndisks++; } else err = errno; return (err); } static void usage(void) { fprintf(stderr, "usage: %s [-S][-c ] [-d ] [-e ]\n" " %*s [-h ] [-m mem-size] \n", progname, (int)strlen(progname), ""); exit(1); } int main(int argc, char** argv) { char *loader; void *h; void (*func)(struct loader_callbacks *, void *, int, int); uint64_t mem_size; int opt, error, need_reinit, memflags; progname = basename(argv[0]); loader = NULL; memflags = 0; mem_size = 256 * MB; consin_fd = STDIN_FILENO; consout_fd = STDOUT_FILENO; while ((opt = getopt(argc, argv, "CSc:d:e:h:l:m:")) != -1) { switch (opt) { case 'c': error = altcons_open(optarg); if (error != 0) errx(EX_USAGE, "Could not open '%s'", optarg); break; case 'd': error = disk_open(optarg); if (error != 0) errx(EX_USAGE, "Could not open '%s'", optarg); break; case 'e': addenv(optarg); break; case 'h': host_base = optarg; break; case 'l': if (loader != NULL) errx(EX_USAGE, "-l can only be given once"); loader = strdup(optarg); if (loader == NULL) err(EX_OSERR, "malloc"); break; case 'm': error = vm_parse_memsize(optarg, &mem_size); if (error != 0) errx(EX_USAGE, "Invalid memsize '%s'", optarg); break; case 'C': memflags |= VM_MEM_F_INCORE; break; case 'S': memflags |= VM_MEM_F_WIRED; break; case '?': usage(); } } argc -= optind; argv += optind; if (argc != 1) usage(); vmname = argv[0]; need_reinit = 0; error = vm_create(vmname); if (error) { if (errno != EEXIST) { perror("vm_create"); exit(1); } need_reinit = 1; } ctx = vm_open(vmname); if (ctx == NULL) { perror("vm_open"); exit(1); } if (need_reinit) { error = vm_reinit(ctx); if (error) { perror("vm_reinit"); exit(1); } } vm_set_memflags(ctx, memflags); error = vm_setup_memory(ctx, mem_size, VM_MMAP_ALL); if (error) { perror("vm_setup_memory"); exit(1); } if (loader == NULL) { loader = strdup("/boot/userboot.so"); if (loader == NULL) err(EX_OSERR, "malloc"); } h = dlopen(loader, RTLD_LOCAL); if (!h) { printf("%s\n", dlerror()); free(loader); return (1); } func = dlsym(h, "loader_main"); if (!func) { printf("%s\n", dlerror()); free(loader); return (1); } tcgetattr(consout_fd, &term); oldterm = term; cfmakeraw(&term); term.c_cflag |= CLOCAL; tcsetattr(consout_fd, TCSAFLUSH, &term); addenv("smbios.bios.vendor=BHYVE"); addenv("boot_serial=1"); func(&cb, NULL, USERBOOT_VERSION_4, ndisks); free(loader); return (0); } Index: projects/release-pkg/usr.sbin/bhyveload =================================================================== --- projects/release-pkg/usr.sbin/bhyveload (revision 297604) +++ projects/release-pkg/usr.sbin/bhyveload (revision 297605) Property changes on: projects/release-pkg/usr.sbin/bhyveload ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/usr.sbin/bhyveload:r296422-297604 Index: projects/release-pkg =================================================================== --- projects/release-pkg (revision 297604) +++ projects/release-pkg (revision 297605) Property changes on: projects/release-pkg ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r297567-297604