2b94c69d1d
attempting to duplicate Windows spinlocks. Windows spinlocks differ from FreeBSD spinlocks in the way they block preemption. FreeBSD spinlocks use critical_enter(), which masks off _all_ interrupts. This prevents any other threads from being scheduled, but it also prevents ISRs from running. In Windows, preemption is achieved by raising the processor IRQL to DISPATCH_LEVEL, which prevents other threads from preempting you, but does _not_ prevent device ISRs from running. (This is essentially what Solaris calls dispatcher locks.) The Windows spinlock itself (kspin_lock) is just an integer value which is atomically set when you acquire the lock and atomically cleared when you release it. FreeBSD doesn't have IRQ levels, so we have to cheat a little by using thread priorities: normal thread priority is PASSIVE_LEVEL, lowest interrupt thread priority is DISPATCH_LEVEL, highest thread priority is DEVICE_LEVEL (PI_REALTIME) and critical_enter() is HIGH_LEVEL. In practice, only PASSIVE_LEVEL and DISPATCH_LEVEL matter to us. The immediate benefit of all this is that I no longer have to rely on a mutex pool. Now, I'm sure many people will be seized by the urge to criticize me for doing an end run around our own spinlock implementation, but it makes more sense to do it this way. Well, it does to me anyway. Overview of the changes: - Properly implement hal_lock(), hal_unlock(), hal_irql(), hal_raise_irql() and hal_lower_irql() so that they more closely resemble their Windows counterparts. The IRQL is determined by thread priority. - Make ntoskrnl_lock_dpc() and ntoskrnl_unlock_dpc() do what they do in Windows, which is to atomically set/clear the lock value. These routines are designed to be called from DISPATCH_LEVEL, and are actually half of the work involved in acquiring/releasing spinlocks. - Add FASTCALL1(), FASTCALL2() and FASTCALL3() macros/wrappers that allow us to call a _fastcall function in spite of the fact that our version of gcc doesn't support __attribute__((__fastcall__)) yet. The macros take 1, 2 or 3 arguments, respectively. We need to call hal_lock(), hal_unlock() etc... ourselves, but can't really invoke the function directly. I could have just made the underlying functions native routines and put _fastcall wrappers around them for the benefit of Windows binaries, but that would create needless bloat. - Remove ndis_mtxpool and all references to it. We don't need it anymore. - Re-implement the NdisSpinLock routines so that they use hal_lock() and friends like they do in Windows. - Use the new spinlock methods for handling lookaside lists and linked list updates in place of the mutex locks that were there before. - Remove mutex locking from ndis_isr() and ndis_intrhand() since they're already called with ndis_intrmtx held in if_ndis.c. - Put ndis_destroy_lock() code under explicit #ifdef notdef/#endif. It turns out there are some drivers which stupidly free the memory in which their spinlocks reside before calling ndis_destroy_lock() on them (touch-after-free bug). The ADMtek wireless driver is guilty of this faux pas. (Why this doesn't clobber Windows I have no idea.) - Make NdisDprAcquireSpinLock() and NdisDprReleaseSpinLock() into real functions instead of aliasing them to NdisAcaquireSpinLock() and NdisReleaseSpinLock(). The Dpr routines use KeAcquireSpinLockAtDpcLevel() level and KeReleaseSpinLockFromDpcLevel(), which acquires the lock without twiddling the IRQL. - In ndis_linksts_done(), do _not_ call ndis_80211_getstate(). Some drivers may call the status/status done callbacks as the result of setting an OID: ndis_80211_getstate() gets OIDs, which means we might cause the driver to recursively access some of its internal structures unexpectedly. The ndis_ticktask() routine will call ndis_80211_getstate() for us eventually anyway. - Fix the channel setting code a little in ndis_80211_setstate(), and initialize the channel to IEEE80211_CHAN_ANYC. (The Microsoft spec says you're not supposed to twiddle the channel in BSS mode; I may need to enforce this later.) This fixes the problems I was having with the ADMtek adm8211 driver: we were setting the channel to a non-standard default, which would cause it to fail to associate in BSS mode. - Use hal_raise_irql() to raise our IRQL to DISPATCH_LEVEL when calling certain miniport routines, per the Microsoft documentation. I think that's everything. Hopefully, other than fixing the ADMtek driver, there should be no apparent change in behavior.
410 lines
11 KiB
C
410 lines
11 KiB
C
/*
|
|
* Copyright (c) 2003
|
|
* Bill Paul <wpaul@windriver.com>. 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 Bill Paul.
|
|
* 4. Neither the name of the author nor the names of any co-contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD
|
|
* 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 <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/errno.h>
|
|
|
|
#include <sys/callout.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/sched.h>
|
|
|
|
#include <sys/systm.h>
|
|
#include <machine/clock.h>
|
|
#include <machine/bus_memio.h>
|
|
#include <machine/bus_pio.h>
|
|
#include <machine/bus.h>
|
|
|
|
#include <sys/bus.h>
|
|
#include <sys/rman.h>
|
|
|
|
#include <compat/ndis/pe_var.h>
|
|
#include <compat/ndis/hal_var.h>
|
|
#include <compat/ndis/ntoskrnl_var.h>
|
|
|
|
#define FUNC void(*)(void)
|
|
|
|
__stdcall static void hal_stall_exec_cpu(uint32_t);
|
|
__stdcall static void hal_writeport_buf_ulong(uint32_t *,
|
|
uint32_t *, uint32_t);
|
|
__stdcall static void hal_writeport_buf_ushort(uint16_t *,
|
|
uint16_t *, uint32_t);
|
|
__stdcall static void hal_writeport_buf_uchar(uint8_t *,
|
|
uint8_t *, uint32_t);
|
|
__stdcall static void hal_writeport_ulong(uint32_t *, uint32_t);
|
|
__stdcall static void hal_writeport_ushort(uint16_t *, uint16_t);
|
|
__stdcall static void hal_writeport_uchar(uint8_t *, uint8_t);
|
|
__stdcall static uint32_t hal_readport_ulong(uint32_t *);
|
|
__stdcall static uint16_t hal_readport_ushort(uint16_t *);
|
|
__stdcall static uint8_t hal_readport_uchar(uint8_t *);
|
|
__stdcall static void hal_readport_buf_ulong(uint32_t *,
|
|
uint32_t *, uint32_t);
|
|
__stdcall static void hal_readport_buf_ushort(uint16_t *,
|
|
uint16_t *, uint32_t);
|
|
__stdcall static void hal_readport_buf_uchar(uint8_t *,
|
|
uint8_t *, uint32_t);
|
|
__stdcall static uint64_t hal_perfcount(uint64_t *);
|
|
__stdcall static void dummy (void);
|
|
|
|
extern struct mtx_pool *ndis_mtxpool;
|
|
|
|
__stdcall static void
|
|
hal_stall_exec_cpu(usecs)
|
|
uint32_t usecs;
|
|
{
|
|
DELAY(usecs);
|
|
return;
|
|
}
|
|
|
|
__stdcall static void
|
|
hal_writeport_ulong(port, val)
|
|
uint32_t *port;
|
|
uint32_t val;
|
|
{
|
|
bus_space_write_4(NDIS_BUS_SPACE_IO, 0x0, (bus_size_t)port, val);
|
|
return;
|
|
}
|
|
|
|
__stdcall static void
|
|
hal_writeport_ushort(port, val)
|
|
uint16_t *port;
|
|
uint16_t val;
|
|
{
|
|
bus_space_write_2(NDIS_BUS_SPACE_IO, 0x0, (bus_size_t)port, val);
|
|
return;
|
|
}
|
|
|
|
__stdcall static void
|
|
hal_writeport_uchar(port, val)
|
|
uint8_t *port;
|
|
uint8_t val;
|
|
{
|
|
bus_space_write_1(NDIS_BUS_SPACE_IO, 0x0, (bus_size_t)port, val);
|
|
return;
|
|
}
|
|
|
|
__stdcall static void
|
|
hal_writeport_buf_ulong(port, val, cnt)
|
|
uint32_t *port;
|
|
uint32_t *val;
|
|
uint32_t cnt;
|
|
{
|
|
bus_space_write_multi_4(NDIS_BUS_SPACE_IO, 0x0,
|
|
(bus_size_t)port, val, cnt);
|
|
return;
|
|
}
|
|
|
|
__stdcall static void
|
|
hal_writeport_buf_ushort(port, val, cnt)
|
|
uint16_t *port;
|
|
uint16_t *val;
|
|
uint32_t cnt;
|
|
{
|
|
bus_space_write_multi_2(NDIS_BUS_SPACE_IO, 0x0,
|
|
(bus_size_t)port, val, cnt);
|
|
return;
|
|
}
|
|
|
|
__stdcall static void
|
|
hal_writeport_buf_uchar(port, val, cnt)
|
|
uint8_t *port;
|
|
uint8_t *val;
|
|
uint32_t cnt;
|
|
{
|
|
bus_space_write_multi_1(NDIS_BUS_SPACE_IO, 0x0,
|
|
(bus_size_t)port, val, cnt);
|
|
return;
|
|
}
|
|
|
|
__stdcall static uint16_t
|
|
hal_readport_ushort(port)
|
|
uint16_t *port;
|
|
{
|
|
return(bus_space_read_2(NDIS_BUS_SPACE_IO, 0x0, (bus_size_t)port));
|
|
}
|
|
|
|
__stdcall static uint32_t
|
|
hal_readport_ulong(port)
|
|
uint32_t *port;
|
|
{
|
|
return(bus_space_read_4(NDIS_BUS_SPACE_IO, 0x0, (bus_size_t)port));
|
|
}
|
|
|
|
__stdcall static uint8_t
|
|
hal_readport_uchar(port)
|
|
uint8_t *port;
|
|
{
|
|
return(bus_space_read_1(NDIS_BUS_SPACE_IO, 0x0, (bus_size_t)port));
|
|
}
|
|
|
|
__stdcall static void
|
|
hal_readport_buf_ulong(port, val, cnt)
|
|
uint32_t *port;
|
|
uint32_t *val;
|
|
uint32_t cnt;
|
|
{
|
|
bus_space_read_multi_4(NDIS_BUS_SPACE_IO, 0x0,
|
|
(bus_size_t)port, val, cnt);
|
|
return;
|
|
}
|
|
|
|
__stdcall static void
|
|
hal_readport_buf_ushort(port, val, cnt)
|
|
uint16_t *port;
|
|
uint16_t *val;
|
|
uint32_t cnt;
|
|
{
|
|
bus_space_read_multi_2(NDIS_BUS_SPACE_IO, 0x0,
|
|
(bus_size_t)port, val, cnt);
|
|
return;
|
|
}
|
|
|
|
__stdcall static void
|
|
hal_readport_buf_uchar(port, val, cnt)
|
|
uint8_t *port;
|
|
uint8_t *val;
|
|
uint32_t cnt;
|
|
{
|
|
bus_space_read_multi_1(NDIS_BUS_SPACE_IO, 0x0,
|
|
(bus_size_t)port, val, cnt);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The spinlock implementation in Windows differs from that of FreeBSD.
|
|
* The basic operation of spinlocks involves two steps: 1) spin in a
|
|
* tight loop while trying to acquire a lock, 2) after obtaining the
|
|
* lock, disable preemption. (Note that on uniprocessor systems, you're
|
|
* allowed to skip the first step and just lock out pre-emption, since
|
|
* it's not possible for you to be in contention with another running
|
|
* thread.) Later, you release the lock then re-enable preemption.
|
|
* The difference between Windows and FreeBSD lies in how preemption
|
|
* is disabled. In FreeBSD, it's done using critical_enter(), which on
|
|
* the x86 arch translates to a cli instruction. This masks off all
|
|
* interrupts, and effectively stops the scheduler from ever running
|
|
* so _nothing_ can execute except the current thread. In Windows,
|
|
* preemption is disabled by raising the processor IRQL to DISPATCH_LEVEL.
|
|
* This stops other threads from running, but does _not_ block device
|
|
* interrupts. This means ISRs can still run, and they can make other
|
|
* threads runable, but those other threads won't be able to execute
|
|
* until the current thread lowers the IRQL to something less than
|
|
* DISPATCH_LEVEL.
|
|
*
|
|
* In FreeBSD, ISRs run in interrupt threads, so to duplicate the
|
|
* Windows notion of IRQLs, we use the following rules:
|
|
*
|
|
* PASSIVE_LEVEL == normal kernel thread priority
|
|
* DISPATCH_LEVEL == lowest interrupt thread priotity (PI_SOFT)
|
|
* DEVICE_LEVEL == highest interrupt thread priority (PI_REALTIME)
|
|
* HIGH_LEVEL == interrupts disabled (critical_enter())
|
|
*
|
|
* Be aware that, at least on the x86 arch, the Windows spinlock
|
|
* functions are divided up in peculiar ways. The actual spinlock
|
|
* functions are KfAcquireSpinLock() and KfReleaseSpinLock(), and
|
|
* they live in HAL.dll. Meanwhile, KeInitializeSpinLock(),
|
|
* KefAcquireSpinLockAtDpcLevel() and KefReleaseSpinLockFromDpcLevel()
|
|
* live in ntoskrnl.exe. Most Windows source code will call
|
|
* KeAcquireSpinLock() and KeReleaseSpinLock(), but these are just
|
|
* macros that call KfAcquireSpinLock() and KfReleaseSpinLock().
|
|
* KefAcquireSpinLockAtDpcLevel() and KefReleaseSpinLockFromDpcLevel()
|
|
* perform the lock aquisition/release functions without doing the
|
|
* IRQL manipulation, and are used when one is already running at
|
|
* DISPATCH_LEVEL. Make sense? Good.
|
|
*
|
|
* According to the Microsoft documentation, any thread that calls
|
|
* KeAcquireSpinLock() must be running at IRQL <= DISPATCH_LEVEL. If
|
|
* we detect someone trying to acquire a spinlock from DEVICE_LEVEL
|
|
* or HIGH_LEVEL, we panic.
|
|
*/
|
|
|
|
__stdcall uint8_t
|
|
hal_lock(/*lock*/void)
|
|
{
|
|
kspin_lock *lock;
|
|
uint8_t oldirql;
|
|
|
|
__asm__ __volatile__ ("" : "=c" (lock));
|
|
|
|
/* I am so going to hell for this. */
|
|
if (hal_irql() > DISPATCH_LEVEL)
|
|
panic("IRQL_NOT_LESS_THAN_OR_EQUAL");
|
|
|
|
oldirql = FASTCALL1(hal_raise_irql, DISPATCH_LEVEL);
|
|
FASTCALL1(ntoskrnl_lock_dpc, lock);
|
|
return(oldirql);
|
|
}
|
|
|
|
__stdcall void
|
|
hal_unlock(/*lock, newirql*/void)
|
|
{
|
|
kspin_lock *lock;
|
|
uint8_t newirql;
|
|
|
|
__asm__ __volatile__ ("" : "=c" (lock), "=d" (newirql));
|
|
|
|
FASTCALL1(ntoskrnl_unlock_dpc, lock);
|
|
FASTCALL1(hal_lower_irql, newirql);
|
|
|
|
return;
|
|
}
|
|
|
|
__stdcall uint8_t
|
|
hal_irql(void)
|
|
{
|
|
if (AT_DISPATCH_LEVEL(curthread))
|
|
return(DISPATCH_LEVEL);
|
|
if (AT_DIRQL_LEVEL(curthread))
|
|
return(DEVICE_LEVEL);
|
|
if (AT_HIGH_LEVEL(curthread))
|
|
return(HIGH_LEVEL);
|
|
return(PASSIVE_LEVEL);
|
|
}
|
|
|
|
__stdcall static uint64_t
|
|
hal_perfcount(freq)
|
|
uint64_t *freq;
|
|
{
|
|
if (freq != NULL)
|
|
*freq = hz;
|
|
|
|
return((uint64_t)ticks);
|
|
}
|
|
|
|
__stdcall uint8_t
|
|
hal_raise_irql(/*irql*/ void)
|
|
{
|
|
uint8_t irql;
|
|
uint8_t oldirql;
|
|
|
|
__asm__ __volatile__ ("" : "=c" (irql));
|
|
|
|
switch(irql) {
|
|
case HIGH_LEVEL:
|
|
oldirql = hal_irql();
|
|
critical_enter();
|
|
break;
|
|
case DEVICE_LEVEL:
|
|
mtx_lock_spin(&sched_lock);
|
|
oldirql = curthread->td_priority;
|
|
sched_prio(curthread, PI_REALTIME);
|
|
mtx_unlock_spin(&sched_lock);
|
|
break;
|
|
case DISPATCH_LEVEL:
|
|
mtx_lock_spin(&sched_lock);
|
|
oldirql = curthread->td_priority;
|
|
sched_prio(curthread, PI_SOFT);
|
|
mtx_unlock_spin(&sched_lock);
|
|
break;
|
|
default:
|
|
panic("can't raise IRQL to unknown level %d", irql);
|
|
break;
|
|
}
|
|
|
|
return(oldirql);
|
|
}
|
|
|
|
__stdcall void
|
|
hal_lower_irql(/*oldirql*/ void)
|
|
{
|
|
uint8_t oldirql;
|
|
uint8_t irql;
|
|
|
|
__asm__ __volatile__ ("" : "=c" (oldirql));
|
|
|
|
irql = hal_irql();
|
|
|
|
switch (irql) {
|
|
case HIGH_LEVEL:
|
|
critical_exit();
|
|
break;
|
|
case DEVICE_LEVEL:
|
|
case DISPATCH_LEVEL:
|
|
mtx_lock_spin(&sched_lock);
|
|
sched_prio(curthread, oldirql);
|
|
mtx_unlock_spin(&sched_lock);
|
|
break;
|
|
default:
|
|
panic("can't lower IRQL to unknown level %d", irql);
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
__stdcall
|
|
static void dummy()
|
|
{
|
|
printf ("hal dummy called...\n");
|
|
return;
|
|
}
|
|
|
|
image_patch_table hal_functbl[] = {
|
|
{ "KeStallExecutionProcessor", (FUNC)hal_stall_exec_cpu },
|
|
{ "WRITE_PORT_ULONG", (FUNC)hal_writeport_ulong },
|
|
{ "WRITE_PORT_USHORT", (FUNC)hal_writeport_ushort },
|
|
{ "WRITE_PORT_UCHAR", (FUNC)hal_writeport_uchar },
|
|
{ "WRITE_PORT_BUFFER_ULONG", (FUNC)hal_writeport_buf_ulong },
|
|
{ "WRITE_PORT_BUFFER_USHORT", (FUNC)hal_writeport_buf_ushort },
|
|
{ "WRITE_PORT_BUFFER_UCHAR", (FUNC)hal_writeport_buf_uchar },
|
|
{ "READ_PORT_ULONG", (FUNC)hal_readport_ulong },
|
|
{ "READ_PORT_USHORT", (FUNC)hal_readport_ushort },
|
|
{ "READ_PORT_UCHAR", (FUNC)hal_readport_uchar },
|
|
{ "READ_PORT_BUFFER_ULONG", (FUNC)hal_readport_buf_ulong },
|
|
{ "READ_PORT_BUFFER_USHORT", (FUNC)hal_readport_buf_ushort },
|
|
{ "READ_PORT_BUFFER_UCHAR", (FUNC)hal_readport_buf_uchar },
|
|
{ "KfAcquireSpinLock", (FUNC)hal_lock },
|
|
{ "KfReleaseSpinLock", (FUNC)hal_unlock },
|
|
{ "KeGetCurrentIrql", (FUNC)hal_irql },
|
|
{ "KeQueryPerformanceCounter", (FUNC)hal_perfcount },
|
|
{ "KfLowerIrql", (FUNC)hal_lower_irql },
|
|
{ "KfRaiseIrql", (FUNC)hal_raise_irql },
|
|
|
|
/*
|
|
* This last entry is a catch-all for any function we haven't
|
|
* implemented yet. The PE import list patching routine will
|
|
* use it for any function that doesn't have an explicit match
|
|
* in this table.
|
|
*/
|
|
|
|
{ NULL, (FUNC)dummy },
|
|
|
|
/* End of list. */
|
|
|
|
{ NULL, NULL },
|
|
};
|