/*-
* SPDX-License-Identifier: BSD-4-Clause
*
* Copyright (c) 2005
* 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/systm.h>
#include <sys/unistd.h>
#include <sys/types.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/mbuf.h>
#include <sys/bus.h>
#include <sys/proc.h>
#include <sys/sched.h>
#include <sys/smp.h>
#include <sys/queue.h>
#ifdef __i386__
#include <machine/segments.h>
#endif
#ifdef __amd64__
#include <machine/fpu.h>
#endif
#include <dev/usb/usb.h>
#include <compat/ndis/pe_var.h>
#include <compat/ndis/cfg_var.h>
#include <compat/ndis/resource_var.h>
#include <compat/ndis/ntoskrnl_var.h>
#include <compat/ndis/ndis_var.h>
#include <compat/ndis/hal_var.h>
#include <compat/ndis/usbd_var.h>
#ifdef __amd64__
struct fpu_cc_ent {
struct fpu_kern_ctx *ctx;
LIST_ENTRY(fpu_cc_ent) entries;
};
static LIST_HEAD(fpu_ctx_free, fpu_cc_ent) fpu_free_head =
LIST_HEAD_INITIALIZER(fpu_free_head);
static LIST_HEAD(fpu_ctx_busy, fpu_cc_ent) fpu_busy_head =
LIST_HEAD_INITIALIZER(fpu_busy_head);
static struct mtx fpu_free_mtx;
static struct mtx fpu_busy_mtx;
#endif
static struct mtx drvdb_mtx;
static STAILQ_HEAD(drvdb, drvdb_ent) drvdb_head;
static driver_object fake_pci_driver; /* serves both PCI and cardbus */
static driver_object fake_pccard_driver;
#ifdef __i386__
static void x86_oldldt(void *);
static void x86_newldt(void *);
struct tid {
void *tid_except_list; /* 0x00 */
uint32_t tid_oldfs; /* 0x04 */
uint32_t tid_selector; /* 0x08 */
struct tid *tid_self; /* 0x0C */
int tid_cpu; /* 0x10 */
};
static struct tid *my_tids;
#endif /* __i386__ */
#define DUMMY_REGISTRY_PATH "\\\\some\\bogus\\path"
int
windrv_libinit(void)
{
STAILQ_INIT(&drvdb_head);
mtx_init(&drvdb_mtx, "Windows driver DB lock",
"Windows internal lock", MTX_DEF);
#ifdef __amd64__
LIST_INIT(&fpu_free_head);
LIST_INIT(&fpu_busy_head);
mtx_init(&fpu_free_mtx, "free fpu context list lock", NULL, MTX_DEF);
mtx_init(&fpu_busy_mtx, "busy fpu context list lock", NULL, MTX_DEF);
#endif
/*
* PCI and pccard devices don't need to use IRPs to
* interact with their bus drivers (usually), so our
* emulated PCI and pccard drivers are just stubs.
* USB devices, on the other hand, do all their I/O
* by exchanging IRPs with the USB bus driver, so
* for that we need to provide emulator dispatcher
* routines, which are in a separate module.
*/
windrv_bus_attach(&fake_pci_driver, "PCI Bus");
windrv_bus_attach(&fake_pccard_driver, "PCCARD Bus");
#ifdef __i386__
/*
* In order to properly support SMP machines, we have
* to modify the GDT on each CPU, since we never know
* on which one we'll end up running.
*/
my_tids = ExAllocatePoolWithTag(NonPagedPool,
sizeof(struct tid) * mp_ncpus, 0);
if (my_tids == NULL)
panic("failed to allocate thread info blocks");
smp_rendezvous(NULL, x86_newldt, NULL, NULL);
#endif
return (0);
}
int
windrv_libfini(void)
{
struct drvdb_ent *d;
#ifdef __amd64__
struct fpu_cc_ent *ent;
#endif
mtx_lock(&drvdb_mtx);
while(STAILQ_FIRST(&drvdb_head) != NULL) {
d = STAILQ_FIRST(&drvdb_head);
STAILQ_REMOVE_HEAD(&drvdb_head, link);
free(d, M_DEVBUF);
}
mtx_unlock(&drvdb_mtx);
RtlFreeUnicodeString(&fake_pci_driver.dro_drivername);
RtlFreeUnicodeString(&fake_pccard_driver.dro_drivername);
mtx_destroy(&drvdb_mtx);
#ifdef __i386__
smp_rendezvous(NULL, x86_oldldt, NULL, NULL);
ExFreePool(my_tids);
#endif
#ifdef __amd64__
while ((ent = LIST_FIRST(&fpu_free_head)) != NULL) {
LIST_REMOVE(ent, entries);
fpu_kern_free_ctx(ent->ctx);
free(ent, M_DEVBUF);
}
mtx_destroy(&fpu_free_mtx);
ent = LIST_FIRST(&fpu_busy_head);
KASSERT(ent == NULL, ("busy fpu context list is not empty"));
mtx_destroy(&fpu_busy_mtx);
#endif
return (0);
}
/*
* Given the address of a driver image, find its corresponding
* driver_object.
*/
driver_object *
windrv_lookup(img, name)
vm_offset_t img;
char *name;
{
struct drvdb_ent *d;
unicode_string us;
ansi_string as;
bzero((char *)&us, sizeof(us));
/* Damn unicode. */
if (name != NULL) {
RtlInitAnsiString(&as, name);
if (RtlAnsiStringToUnicodeString(&us, &as, TRUE))
return (NULL);
}
mtx_lock(&drvdb_mtx);
STAILQ_FOREACH(d, &drvdb_head, link) {
if (d->windrv_object->dro_driverstart == (void *)img ||
(bcmp((char *)d->windrv_object->dro_drivername.us_buf,
(char *)us.us_buf, us.us_len) == 0 && us.us_len)) {
mtx_unlock(&drvdb_mtx);
if (name != NULL)
ExFreePool(us.us_buf);
return (d->windrv_object);
}
}
mtx_unlock(&drvdb_mtx);
if (name != NULL)
RtlFreeUnicodeString(&us);
return (NULL);
}
struct drvdb_ent *
windrv_match(matchfunc, ctx)
matchfuncptr matchfunc;
void *ctx;
{
struct drvdb_ent *d;
int match;
mtx_lock(&drvdb_mtx);
STAILQ_FOREACH(d, &drvdb_head, link) {
if (d->windrv_devlist == NULL)
continue;
match = matchfunc(d->windrv_bustype, d->windrv_devlist, ctx);
if (match == TRUE) {
mtx_unlock(&drvdb_mtx);
return (d);
}
}
mtx_unlock(&drvdb_mtx);
return (NULL);
}
/*
* Remove a driver_object from our datatabase and destroy it. Throw
* away any custom driver extension info that may have been added.
*/
int
windrv_unload(mod, img, len)
module_t mod;
vm_offset_t img;
int len;
{
struct drvdb_ent *db, *r = NULL;
driver_object *drv;
device_object *d, *pdo;
device_t dev;
list_entry *e;
drv = windrv_lookup(img, NULL);
/*
* When we unload a driver image, we need to force a
* detach of any devices that might be using it. We
* need the PDOs of all attached devices for this.
* Getting at them is a little hard. We basically
* have to walk the device lists of all our bus
* drivers.
*/
mtx_lock(&drvdb_mtx);
STAILQ_FOREACH(db, &drvdb_head, link) {
/*
* Fake bus drivers have no devlist info.
* If this driver has devlist info, it's
* a loaded Windows driver and has no PDOs,
* so skip it.
*/
if (db->windrv_devlist != NULL)
continue;
pdo = db->windrv_object->dro_devobj;
while (pdo != NULL) {
d = pdo->do_attacheddev;
if (d->do_drvobj != drv) {
pdo = pdo->do_nextdev;
continue;
}
dev = pdo->do_devext;
pdo = pdo->do_nextdev;
mtx_unlock(&drvdb_mtx);
device_detach(dev);
mtx_lock(&drvdb_mtx);
}
}
STAILQ_FOREACH(db, &drvdb_head, link) {
if (db->windrv_object->dro_driverstart == (void *)img) {
r = db;
STAILQ_REMOVE(&drvdb_head, db, drvdb_ent, link);
break;
}
}
mtx_unlock(&drvdb_mtx);
if (r == NULL)
return (ENOENT);
if (drv == NULL)
return (ENOENT);
/*
* Destroy any custom extensions that may have been added.
*/
drv = r->windrv_object;
while (!IsListEmpty(&drv->dro_driverext->dre_usrext)) {
e = RemoveHeadList(&drv->dro_driverext->dre_usrext);
ExFreePool(e);
}
/* Free the driver extension */
free(drv->dro_driverext, M_DEVBUF);
/* Free the driver name */
RtlFreeUnicodeString(&drv->dro_drivername);
/* Free driver object */
free(drv, M_DEVBUF);
/* Free our DB handle */
free(r, M_DEVBUF);
return (0);
}
#define WINDRV_LOADED htonl(0x42534F44)
#ifdef __amd64__
static void
patch_user_shared_data_address(vm_offset_t img, size_t len)
{
unsigned long i, n, max_addr, *addr;
n = len - sizeof(unsigned long);
max_addr = KI_USER_SHARED_DATA + sizeof(kuser_shared_data);
for (i = 0; i < n; i++) {
addr = (unsigned long *)(img + i);
if (*addr >= KI_USER_SHARED_DATA && *addr < max_addr) {
*addr -= KI_USER_SHARED_DATA;
*addr += (unsigned long)&kuser_shared_data;
}
}
}
#endif
/*
* Loader routine for actual Windows driver modules, ultimately
* calls the driver's DriverEntry() routine.
*/
int
windrv_load(mod, img, len, bustype, devlist, regvals)
module_t mod;
vm_offset_t img;
int len;
interface_type bustype;
void *devlist;
ndis_cfg *regvals;
{
image_import_descriptor imp_desc;
image_optional_header opt_hdr;
driver_entry entry;
struct drvdb_ent *new;
struct driver_object *drv;
int status;
uint32_t *ptr;
ansi_string as;
/*
* First step: try to relocate and dynalink the executable
* driver image.
*/
ptr = (uint32_t *)(img + 8);
if (*ptr == WINDRV_LOADED)
goto skipreloc;
/* Perform text relocation */
if (pe_relocate(img))
return (ENOEXEC);
/* Dynamically link the NDIS.SYS routines -- required. */
if (pe_patch_imports(img, "NDIS", ndis_functbl))
return (ENOEXEC);
/* Dynamically link the HAL.dll routines -- optional. */
if (pe_get_import_descriptor(img, &imp_desc, "HAL") == 0) {
if (pe_patch_imports(img, "HAL", hal_functbl))
return (ENOEXEC);
}
/* Dynamically link ntoskrnl.exe -- optional. */
if (pe_get_import_descriptor(img, &imp_desc, "ntoskrnl") == 0) {
if (pe_patch_imports(img, "ntoskrnl", ntoskrnl_functbl))
return (ENOEXEC);
}
#ifdef __amd64__
patch_user_shared_data_address(img, len);
#endif
/* Dynamically link USBD.SYS -- optional */
if (pe_get_import_descriptor(img, &imp_desc, "USBD") == 0) {
if (pe_patch_imports(img, "USBD", usbd_functbl))
return (ENOEXEC);
}
*ptr = WINDRV_LOADED;
skipreloc:
/* Next step: find the driver entry point. */
pe_get_optional_header(img, &opt_hdr);
entry = (driver_entry)pe_translate_addr(img, opt_hdr.ioh_entryaddr);
/* Next step: allocate and store a driver object. */
new = malloc(sizeof(struct drvdb_ent), M_DEVBUF, M_NOWAIT|M_ZERO);
if (new == NULL)
return (ENOMEM);
drv = malloc(sizeof(driver_object), M_DEVBUF, M_NOWAIT|M_ZERO);
if (drv == NULL) {
free (new, M_DEVBUF);
return (ENOMEM);
}
/* Allocate a driver extension structure too. */
drv->dro_driverext = malloc(sizeof(driver_extension),
M_DEVBUF, M_NOWAIT|M_ZERO);
if (drv->dro_driverext == NULL) {
free(new, M_DEVBUF);
free(drv, M_DEVBUF);
return (ENOMEM);
}
InitializeListHead((&drv->dro_driverext->dre_usrext));
drv->dro_driverstart = (void *)img;
drv->dro_driversize = len;
RtlInitAnsiString(&as, DUMMY_REGISTRY_PATH);
if (RtlAnsiStringToUnicodeString(&drv->dro_drivername, &as, TRUE)) {
free(new, M_DEVBUF);
free(drv, M_DEVBUF);
return (ENOMEM);
}
new->windrv_object = drv;
new->windrv_regvals = regvals;
new->windrv_devlist = devlist;
new->windrv_bustype = bustype;
/* Now call the DriverEntry() function. */
status = MSCALL2(entry, drv, &drv->dro_drivername);
if (status != STATUS_SUCCESS) {
RtlFreeUnicodeString(&drv->dro_drivername);
free(drv, M_DEVBUF);
free(new, M_DEVBUF);
return (ENODEV);
}
mtx_lock(&drvdb_mtx);
STAILQ_INSERT_HEAD(&drvdb_head, new, link);
mtx_unlock(&drvdb_mtx);
return (0);
}
/*
* Make a new Physical Device Object for a device that was
* detected/plugged in. For us, the PDO is just a way to
* get at the device_t.
*/
int
windrv_create_pdo(drv, bsddev)
driver_object *drv;
device_t bsddev;
{
device_object *dev;
/*
* This is a new physical device object, which technically
* is the "top of the stack." Consequently, we don't do
* an IoAttachDeviceToDeviceStack() here.
*/
mtx_lock(&drvdb_mtx);
IoCreateDevice(drv, 0, NULL, FILE_DEVICE_UNKNOWN, 0, FALSE, &dev);
mtx_unlock(&drvdb_mtx);
/* Stash pointer to our BSD device handle. */
dev->do_devext = bsddev;
return (STATUS_SUCCESS);
}
void
windrv_destroy_pdo(drv, bsddev)
driver_object *drv;
device_t bsddev;
{
device_object *pdo;
pdo = windrv_find_pdo(drv, bsddev);
/* Remove reference to device_t */
pdo->do_devext = NULL;
mtx_lock(&drvdb_mtx);
IoDeleteDevice(pdo);
mtx_unlock(&drvdb_mtx);
}
/*
* Given a device_t, find the corresponding PDO in a driver's
* device list.
*/
device_object *
windrv_find_pdo(drv, bsddev)
driver_object *drv;
device_t bsddev;
{
device_object *pdo;
mtx_lock(&drvdb_mtx);
pdo = drv->dro_devobj;
while (pdo != NULL) {
if (pdo->do_devext == bsddev) {
mtx_unlock(&drvdb_mtx);
return (pdo);
}
pdo = pdo->do_nextdev;
}
mtx_unlock(&drvdb_mtx);
return (NULL);
}
/*
* Add an internally emulated driver to the database. We need this
* to set up an emulated bus driver so that it can receive IRPs.
*/
int
windrv_bus_attach(drv, name)
driver_object *drv;
char *name;
{
struct drvdb_ent *new;
ansi_string as;
new = malloc(sizeof(struct drvdb_ent), M_DEVBUF, M_NOWAIT|M_ZERO);
if (new == NULL)
return (ENOMEM);
RtlInitAnsiString(&as, name);
if (RtlAnsiStringToUnicodeString(&drv->dro_drivername, &as, TRUE))
{
free(new, M_DEVBUF);
return (ENOMEM);
}
/*
* Set up a fake image pointer to avoid false matches
* in windrv_lookup().
*/
drv->dro_driverstart = (void *)0xFFFFFFFF;
new->windrv_object = drv;
new->windrv_devlist = NULL;
new->windrv_regvals = NULL;
mtx_lock(&drvdb_mtx);
STAILQ_INSERT_HEAD(&drvdb_head, new, link);
mtx_unlock(&drvdb_mtx);
return (0);
}
#ifdef __amd64__
extern void x86_64_wrap(void);
extern void x86_64_wrap_call(void);
extern void x86_64_wrap_end(void);
int
windrv_wrap(func, wrap, argcnt, ftype)
funcptr func;
funcptr *wrap;
int argcnt;
int ftype;
{
funcptr p;
vm_offset_t *calladdr;
vm_offset_t wrapstart, wrapend, wrapcall;
wrapstart = (vm_offset_t)&x86_64_wrap;
wrapend = (vm_offset_t)&x86_64_wrap_end;
wrapcall = (vm_offset_t)&x86_64_wrap_call;
/* Allocate a new wrapper instance. */
p = malloc((wrapend - wrapstart), M_DEVBUF, M_NOWAIT);
if (p == NULL)
return (ENOMEM);
/* Copy over the code. */
bcopy((char *)wrapstart, p, (wrapend - wrapstart));
/* Insert the function address into the new wrapper instance. */
calladdr = (uint64_t *)((char *)p + (wrapcall - wrapstart) + 2);
*calladdr = (vm_offset_t)func;
*wrap = p;
return (0);
}
static struct fpu_cc_ent *
request_fpu_cc_ent(void)
{
struct fpu_cc_ent *ent;
mtx_lock(&fpu_free_mtx);
if ((ent = LIST_FIRST(&fpu_free_head)) != NULL) {
LIST_REMOVE(ent, entries);
mtx_unlock(&fpu_free_mtx);
mtx_lock(&fpu_busy_mtx);
LIST_INSERT_HEAD(&fpu_busy_head, ent, entries);
mtx_unlock(&fpu_busy_mtx);
return (ent);
}
mtx_unlock(&fpu_free_mtx);
if ((ent = malloc(sizeof(struct fpu_cc_ent), M_DEVBUF, M_NOWAIT |
M_ZERO)) != NULL) {
ent->ctx = fpu_kern_alloc_ctx(FPU_KERN_NORMAL |
FPU_KERN_NOWAIT);
if (ent->ctx != NULL) {
mtx_lock(&fpu_busy_mtx);
LIST_INSERT_HEAD(&fpu_busy_head, ent, entries);
mtx_unlock(&fpu_busy_mtx);
} else {
free(ent, M_DEVBUF);
ent = NULL;
}
}
return (ent);
}
static void
release_fpu_cc_ent(struct fpu_cc_ent *ent)
{
mtx_lock(&fpu_busy_mtx);
LIST_REMOVE(ent, entries);
mtx_unlock(&fpu_busy_mtx);
mtx_lock(&fpu_free_mtx);
LIST_INSERT_HEAD(&fpu_free_head, ent, entries);
mtx_unlock(&fpu_free_mtx);
}
uint64_t
_x86_64_call1(void *fn, uint64_t a)
{
struct fpu_cc_ent *ent;
uint64_t ret;
if ((ent = request_fpu_cc_ent()) == NULL)
return (ENOMEM);
fpu_kern_enter(curthread, ent->ctx, FPU_KERN_NORMAL);
ret = x86_64_call1(fn, a);
fpu_kern_leave(curthread, ent->ctx);
release_fpu_cc_ent(ent);
return (ret);
}
uint64_t
_x86_64_call2(void *fn, uint64_t a, uint64_t b)
{
struct fpu_cc_ent *ent;
uint64_t ret;
if ((ent = request_fpu_cc_ent()) == NULL)
return (ENOMEM);
fpu_kern_enter(curthread, ent->ctx, FPU_KERN_NORMAL);
ret = x86_64_call2(fn, a, b);
fpu_kern_leave(curthread, ent->ctx);
release_fpu_cc_ent(ent);
return (ret);
}
uint64_t
_x86_64_call3(void *fn, uint64_t a, uint64_t b, uint64_t c)
{
struct fpu_cc_ent *ent;
uint64_t ret;
if ((ent = request_fpu_cc_ent()) == NULL)
return (ENOMEM);
fpu_kern_enter(curthread, ent->ctx, FPU_KERN_NORMAL);
ret = x86_64_call3(fn, a, b, c);
fpu_kern_leave(curthread, ent->ctx);
release_fpu_cc_ent(ent);
return (ret);
}
uint64_t
_x86_64_call4(void *fn, uint64_t a, uint64_t b, uint64_t c, uint64_t d)
{
struct fpu_cc_ent *ent;
uint64_t ret;
if ((ent = request_fpu_cc_ent()) == NULL)
return (ENOMEM);
fpu_kern_enter(curthread, ent->ctx, FPU_KERN_NORMAL);
ret = x86_64_call4(fn, a, b, c, d);
fpu_kern_leave(curthread, ent->ctx);
release_fpu_cc_ent(ent);
return (ret);
}
uint64_t
_x86_64_call5(void *fn, uint64_t a, uint64_t b, uint64_t c, uint64_t d,
uint64_t e)
{
struct fpu_cc_ent *ent;
uint64_t ret;
if ((ent = request_fpu_cc_ent()) == NULL)
return (ENOMEM);
fpu_kern_enter(curthread, ent->ctx, FPU_KERN_NORMAL);
ret = x86_64_call5(fn, a, b, c, d, e);
fpu_kern_leave(curthread, ent->ctx);
release_fpu_cc_ent(ent);
return (ret);
}
uint64_t
_x86_64_call6(void *fn, uint64_t a, uint64_t b, uint64_t c, uint64_t d,
uint64_t e, uint64_t f)
{
struct fpu_cc_ent *ent;
uint64_t ret;
if ((ent = request_fpu_cc_ent()) == NULL)
return (ENOMEM);
fpu_kern_enter(curthread, ent->ctx, FPU_KERN_NORMAL);
ret = x86_64_call6(fn, a, b, c, d, e, f);
fpu_kern_leave(curthread, ent->ctx);
release_fpu_cc_ent(ent);
return (ret);
}
#endif /* __amd64__ */
#ifdef __i386__
struct x86desc {
uint16_t x_lolimit;
uint16_t x_base0;
uint8_t x_base1;
uint8_t x_flags;
uint8_t x_hilimit;
uint8_t x_base2;
};
struct gdt {
uint16_t limit;
void *base;
} __attribute__((__packed__));
extern uint16_t x86_getfs(void);
extern void x86_setfs(uint16_t);
extern void *x86_gettid(void);
extern void x86_critical_enter(void);
extern void x86_critical_exit(void);
extern void x86_getldt(struct gdt *, uint16_t *);
extern void x86_setldt(struct gdt *, uint16_t);
#define SEL_LDT 4 /* local descriptor table */
#define SEL_TO_FS(x) (((x) << 3))
/*
* FreeBSD 6.0 and later has a special GDT segment reserved
* specifically for us, so if GNDIS_SEL is defined, use that.
* If not, use GTGATE_SEL, which is uninitialized and infrequently
* used.
*/
#ifdef GNDIS_SEL
#define FREEBSD_EMPTYSEL GNDIS_SEL
#else
#define FREEBSD_EMPTYSEL GTGATE_SEL /* slot 7 */
#endif
/*
* The meanings of various bits in a descriptor vary a little
* depending on whether the descriptor will be used as a
* code, data or system descriptor. (And that in turn depends
* on which segment register selects the descriptor.)
* We're only trying to create a data segment, so the definitions
* below are the ones that apply to a data descriptor.
*/
#define SEGFLAGLO_PRESENT 0x80 /* segment is present */
#define SEGFLAGLO_PRIVLVL 0x60 /* privlevel needed for this seg */
#define SEGFLAGLO_CD 0x10 /* 1 = code/data, 0 = system */
#define SEGFLAGLO_MBZ 0x08 /* must be zero */
#define SEGFLAGLO_EXPANDDOWN 0x04 /* limit expands down */
#define SEGFLAGLO_WRITEABLE 0x02 /* segment is writeable */
#define SEGGLAGLO_ACCESSED 0x01 /* segment has been accessed */
#define SEGFLAGHI_GRAN 0x80 /* granularity, 1 = byte, 0 = page */
#define SEGFLAGHI_BIG 0x40 /* 1 = 32 bit stack, 0 = 16 bit */
/*
* Context switch from UNIX to Windows. Save the existing value
* of %fs for this processor, then change it to point to our
* fake TID. Note that it is also possible to pin ourselves
* to our current CPU, though I'm not sure this is really
* necessary. It depends on whether or not an interrupt might
* preempt us while Windows code is running and we wind up
* scheduled onto another CPU as a result. So far, it doesn't
* seem like this is what happens.
*/
void
ctxsw_utow(void)
{
struct tid *t;
t = &my_tids[curthread->td_oncpu];
/*
* Ugly hack. During system bootstrap (cold == 1), only CPU 0
* is running. So if we were loaded at bootstrap, only CPU 0
* will have our special GDT entry. This is a problem for SMP
* systems, so to deal with this, we check here to make sure
* the TID for this processor has been initialized, and if it
* hasn't, we need to do it right now or else things will
* explode.
*/
if (t->tid_self != t)
x86_newldt(NULL);
x86_critical_enter();
t->tid_oldfs = x86_getfs();
t->tid_cpu = curthread->td_oncpu;
sched_pin();
x86_setfs(SEL_TO_FS(t->tid_selector));
x86_critical_exit();
/* Now entering Windows land, population: you. */
}
/*
* Context switch from Windows back to UNIX. Restore %fs to
* its previous value. This always occurs after a call to
* ctxsw_utow().
*/
void
ctxsw_wtou(void)
{
struct tid *t;
x86_critical_enter();
t = x86_gettid();
x86_setfs(t->tid_oldfs);
sched_unpin();
x86_critical_exit();
/* Welcome back to UNIX land, we missed you. */
#ifdef EXTRA_SANITY
if (t->tid_cpu != curthread->td_oncpu)
panic("ctxsw GOT MOVED TO OTHER CPU!");
#endif
}
static int windrv_wrap_stdcall(funcptr, funcptr *, int);
static int windrv_wrap_fastcall(funcptr, funcptr *, int);
static int windrv_wrap_regparm(funcptr, funcptr *);
extern void x86_fastcall_wrap(void);
extern void x86_fastcall_wrap_call(void);
extern void x86_fastcall_wrap_arg(void);
extern void x86_fastcall_wrap_end(void);
static int
windrv_wrap_fastcall(func, wrap, argcnt)
funcptr func;
funcptr *wrap;
int8_t argcnt;
{
funcptr p;
vm_offset_t *calladdr;
uint8_t *argaddr;
vm_offset_t wrapstart, wrapend, wrapcall, wraparg;
wrapstart = (vm_offset_t)&x86_fastcall_wrap;
wrapend = (vm_offset_t)&x86_fastcall_wrap_end;
wrapcall = (vm_offset_t)&x86_fastcall_wrap_call;
wraparg = (vm_offset_t)&x86_fastcall_wrap_arg;
/* Allocate a new wrapper instance. */
p = malloc((wrapend - wrapstart), M_DEVBUF, M_NOWAIT);
if (p == NULL)
return (ENOMEM);
/* Copy over the code. */
bcopy((char *)wrapstart, p, (wrapend - wrapstart));
/* Insert the function address into the new wrapper instance. */
calladdr = (vm_offset_t *)((char *)p + ((wrapcall - wrapstart) + 1));
*calladdr = (vm_offset_t)func;
argcnt -= 2;
if (argcnt < 1)
argcnt = 0;
argaddr = (u_int8_t *)((char *)p + ((wraparg - wrapstart) + 1));
*argaddr = argcnt * sizeof(uint32_t);
*wrap = p;
return (0);
}
extern void x86_stdcall_wrap(void);
extern void x86_stdcall_wrap_call(void);
extern void x86_stdcall_wrap_arg(void);
extern void x86_stdcall_wrap_end(void);
static int
windrv_wrap_stdcall(func, wrap, argcnt)
funcptr func;
funcptr *wrap;
uint8_t argcnt;
{
funcptr p;
vm_offset_t *calladdr;
uint8_t *argaddr;
vm_offset_t wrapstart, wrapend, wrapcall, wraparg;
wrapstart = (vm_offset_t)&x86_stdcall_wrap;
wrapend = (vm_offset_t)&x86_stdcall_wrap_end;
wrapcall = (vm_offset_t)&x86_stdcall_wrap_call;
wraparg = (vm_offset_t)&x86_stdcall_wrap_arg;
/* Allocate a new wrapper instance. */
p = malloc((wrapend - wrapstart), M_DEVBUF, M_NOWAIT);
if (p == NULL)
return (ENOMEM);
/* Copy over the code. */
bcopy((char *)wrapstart, p, (wrapend - wrapstart));
/* Insert the function address into the new wrapper instance. */
calladdr = (vm_offset_t *)((char *)p + ((wrapcall - wrapstart) + 1));
*calladdr = (vm_offset_t)func;
argaddr = (u_int8_t *)((char *)p + ((wraparg - wrapstart) + 1));
*argaddr = argcnt * sizeof(uint32_t);
*wrap = p;
return (0);
}
extern void x86_regparm_wrap(void);
extern void x86_regparm_wrap_call(void);
extern void x86_regparm_wrap_end(void);
static int
windrv_wrap_regparm(func, wrap)
funcptr func;
funcptr *wrap;
{
funcptr p;
vm_offset_t *calladdr;
vm_offset_t wrapstart, wrapend, wrapcall;
wrapstart = (vm_offset_t)&x86_regparm_wrap;
wrapend = (vm_offset_t)&x86_regparm_wrap_end;
wrapcall = (vm_offset_t)&x86_regparm_wrap_call;
/* Allocate a new wrapper instance. */
p = malloc((wrapend - wrapstart), M_DEVBUF, M_NOWAIT);
if (p == NULL)
return (ENOMEM);
/* Copy over the code. */
bcopy(x86_regparm_wrap, p, (wrapend - wrapstart));
/* Insert the function address into the new wrapper instance. */
calladdr = (vm_offset_t *)((char *)p + ((wrapcall - wrapstart) + 1));
*calladdr = (vm_offset_t)func;
*wrap = p;
return (0);
}
int
windrv_wrap(func, wrap, argcnt, ftype)
funcptr func;
funcptr *wrap;
int argcnt;
int ftype;
{
switch(ftype) {
case WINDRV_WRAP_FASTCALL:
return (windrv_wrap_fastcall(func, wrap, argcnt));
case WINDRV_WRAP_STDCALL:
return (windrv_wrap_stdcall(func, wrap, argcnt));
case WINDRV_WRAP_REGPARM:
return (windrv_wrap_regparm(func, wrap));
case WINDRV_WRAP_CDECL:
return (windrv_wrap_stdcall(func, wrap, 0));
default:
break;
}
return (EINVAL);
}
static void
x86_oldldt(dummy)
void *dummy;
{
struct x86desc *gdt;
struct gdt gtable;
uint16_t ltable;
mtx_lock_spin(&dt_lock);
/* Grab location of existing GDT. */
x86_getldt(>able, <able);
/* Find the slot we updated. */
gdt = gtable.base;
gdt += FREEBSD_EMPTYSEL;
/* Empty it out. */
bzero((char *)gdt, sizeof(struct x86desc));
/* Restore GDT. */
x86_setldt(>able, ltable);
mtx_unlock_spin(&dt_lock);
}
static void
x86_newldt(dummy)
void *dummy;
{
struct gdt gtable;
uint16_t ltable;
struct x86desc *l;
struct thread *t;
t = curthread;
mtx_lock_spin(&dt_lock);
/* Grab location of existing GDT. */
x86_getldt(>able, <able);
/* Get pointer to the GDT table. */
l = gtable.base;
/* Get pointer to empty slot */
l += FREEBSD_EMPTYSEL;
/* Initialize TID for this CPU. */
my_tids[t->td_oncpu].tid_selector = FREEBSD_EMPTYSEL;
my_tids[t->td_oncpu].tid_self = &my_tids[t->td_oncpu];
/* Set up new GDT entry. */
l->x_lolimit = sizeof(struct tid);
l->x_hilimit = SEGFLAGHI_GRAN|SEGFLAGHI_BIG;
l->x_base0 = (vm_offset_t)(&my_tids[t->td_oncpu]) & 0xFFFF;
l->x_base1 = ((vm_offset_t)(&my_tids[t->td_oncpu]) >> 16) & 0xFF;
l->x_base2 = ((vm_offset_t)(&my_tids[t->td_oncpu]) >> 24) & 0xFF;
l->x_flags = SEGFLAGLO_PRESENT|SEGFLAGLO_CD|SEGFLAGLO_WRITEABLE;
/* Update the GDT. */
x86_setldt(>able, ltable);
mtx_unlock_spin(&dt_lock);
/* Whew. */
}
#endif /* __i386__ */
int
windrv_unwrap(func)
funcptr func;
{
free(func, M_DEVBUF);
return (0);
}