#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/filio.h>
#include <sys/ioccom.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/mman.h>
#include <sys/signalvar.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#include <vm/vm.h>
#include <vm/vm_kern.h>
#include <vm/pmap.h>
#include <vm/vm_extern.h>
#include <machine/resource.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <dev/tdfx/tdfx_io.h>
#include <dev/tdfx/tdfx_vars.h>
#include <dev/tdfx/tdfx_pci.h>
static devclass_t tdfx_devclass;
static int tdfx_count = 0;
static device_method_t tdfx_methods[] = {
DEVMETHOD(device_probe, tdfx_probe),
DEVMETHOD(device_attach, tdfx_attach),
DEVMETHOD(device_detach, tdfx_detach),
DEVMETHOD(device_shutdown, tdfx_shutdown),
DEVMETHOD_END
};
static MALLOC_DEFINE(M_TDFX,"tdfx_driver","3DFX Graphics[/2D]/3D Accelerators");
static struct cdevsw tdfx_cdev = {
.d_version = D_VERSION,
.d_flags = D_NEEDGIANT,
.d_open = tdfx_open,
.d_close = tdfx_close,
.d_ioctl = tdfx_ioctl,
.d_mmap = tdfx_mmap,
.d_name = "tdfx",
};
static int
tdfx_probe(device_t dev)
{
switch(pci_get_devid(dev)) {
case PCI_DEVICE_ALLIANCE_AT3D:
device_set_desc(dev, "ProMotion At3D 3D Accelerator");
return BUS_PROBE_DEFAULT;
case PCI_DEVICE_3DFX_VOODOO2:
device_set_desc(dev, "3DFX Voodoo II 3D Accelerator");
return BUS_PROBE_DEFAULT;
case PCI_DEVICE_3DFX_VOODOO1:
device_set_desc(dev, "3DFX Voodoo Graphics 3D Accelerator");
return BUS_PROBE_DEFAULT;
}
return ENXIO;
}
static int
tdfx_attach(device_t dev) {
struct tdfx_softc *tdfx_info;
int rid = PCIR_BAR(0);
tdfx_count++;
tdfx_info = device_get_softc(dev);
tdfx_info->dev = dev;
tdfx_info->vendor = pci_get_vendor(dev);
tdfx_info->type = pci_get_devid(dev) >> 16;
tdfx_info->bus = pci_get_bus(dev);
tdfx_info->dv = pci_get_slot(dev);
tdfx_info->curFile = NULL;
tdfx_info->addr0 = (pci_read_config(dev, 0x10, 4) & 0xffff0000);
#ifdef DEBUG
device_printf(dev, "Base0 @ 0x%x\n", tdfx_info->addr0);
#endif
tdfx_info->memrange = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&rid, RF_ACTIVE | RF_SHAREABLE);
if(tdfx_info->memrange == NULL) {
#ifdef DEBUG
device_printf(dev, "Error mapping mem, won't be able to use mmap()\n");
#endif
tdfx_info->memrid = 0;
}
else {
tdfx_info->memrid = rid;
#ifdef DEBUG
device_printf(dev, "Mapped to: 0x%x\n",
(unsigned int)rman_get_start(tdfx_info->memrange));
#endif
}
if(pci_get_devid(dev) == PCI_DEVICE_3DFX_VOODOO3 ||
pci_get_devid(dev) == PCI_DEVICE_3DFX_BANSHEE) {
rid = 0x14;
tdfx_info->addr1 = (pci_read_config(dev, 0x14, 4) & 0xffff0000);
#ifdef DEBUG
device_printf(dev, "Base1 @ 0x%x\n", tdfx_info->addr1);
#endif
tdfx_info->memrange2 = bus_alloc_resource_any(dev,
SYS_RES_MEMORY, &rid, RF_ACTIVE | RF_SHAREABLE);
if(tdfx_info->memrange2 == NULL) {
#ifdef DEBUG
device_printf(dev, "Mem1 couldn't be allocated, glide may not work.");
#endif
tdfx_info->memrid2 = 0;
}
else {
tdfx_info->memrid2 = rid;
}
rid = PCIR_IOBASE0_2;
tdfx_info->pio0 = pci_read_config(dev, 0x2c, 2);
tdfx_info->pio0max = pci_read_config(dev, 0x30, 2) + tdfx_info->pio0;
tdfx_info->piorange = bus_alloc_resource_any(dev,
SYS_RES_IOPORT, &rid, RF_ACTIVE | RF_SHAREABLE);
if(tdfx_info->piorange == NULL) {
#ifdef DEBUG
device_printf(dev, "Couldn't map PIO range.");
#endif
tdfx_info->piorid = 0;
}
else {
tdfx_info->piorid = rid;
}
} else {
tdfx_info->addr1 = 0;
tdfx_info->memrange2 = NULL;
tdfx_info->piorange = NULL;
}
if(tdfx_setmtrr(dev) != 0) {
#ifdef DEBUG
device_printf(dev, "Some weird error setting MTRRs");
#endif
return -1;
}
tdfx_info->devt = make_dev(&tdfx_cdev, device_get_unit(dev),
UID_ROOT, GID_WHEEL, 0600, "3dfx%x", device_get_unit(dev));
tdfx_info->devt->si_drv1 = tdfx_info;
return 0;
}
static int
tdfx_detach(device_t dev) {
struct tdfx_softc* tdfx_info;
int retval __unused;
tdfx_info = device_get_softc(dev);
bus_release_resource(dev, SYS_RES_MEMORY, tdfx_info->memrid,
tdfx_info->memrange);
if(pci_get_devid(dev) == PCI_DEVICE_3DFX_BANSHEE ||
pci_get_devid(dev) == PCI_DEVICE_3DFX_VOODOO3) {
if(tdfx_info->memrange2 != NULL)
bus_release_resource(dev, SYS_RES_MEMORY, tdfx_info->memrid2,
tdfx_info->memrange);
}
retval = tdfx_clrmtrr(dev);
#ifdef DEBUG
if(retval != 0)
printf("tdfx: For some reason, I couldn't clear the mtrr\n");
#endif
destroy_dev(tdfx_info->devt);
return(0);
}
static int
tdfx_shutdown(device_t dev) {
#ifdef DEBUG
device_printf(dev, "tdfx: Device Shutdown\n");
#endif
return 0;
}
static int
tdfx_clrmtrr(device_t dev) {
int retval, act;
struct tdfx_softc *tdfx_info = device_get_softc(dev);
act = MEMRANGE_SET_REMOVE;
retval = mem_range_attr_set(&tdfx_info->mrdesc, &act);
return retval;
}
static int
tdfx_setmtrr(device_t dev) {
int retval = 0, act;
struct tdfx_softc *tdfx_info = device_get_softc(dev);
if((pci_get_devid(dev) == PCI_DEVICE_3DFX_VOODOO1) || (pci_get_devid(dev) ==
PCI_DEVICE_3DFX_VOODOO2)) {
tdfx_info->mrdesc.mr_len = 0x400000;
tdfx_info->mrdesc.mr_base = tdfx_info->addr0 & 0xfffe0000;
}
else if((pci_get_devid(dev) == PCI_DEVICE_3DFX_VOODOO3) ||
(pci_get_devid(dev) == PCI_DEVICE_3DFX_BANSHEE)) {
tdfx_info->mrdesc.mr_len = 0x1000000;
tdfx_info->mrdesc.mr_base = tdfx_info->addr1 & 0xfffe0000;
}
else
return 0;
tdfx_info->mrdesc.mr_flags = MDF_WRITECOMBINE;
bcopy("tdfx", &tdfx_info->mrdesc.mr_owner, 4);
act = MEMRANGE_SET_UPDATE;
retval = mem_range_attr_set(&tdfx_info->mrdesc, &act);
if(retval == 0) {
#ifdef DEBUG
device_printf(dev, "MTRR Set Correctly for tdfx\n");
#endif
} else if((pci_get_devid(dev) == PCI_DEVICE_3DFX_VOODOO2) ||
(pci_get_devid(dev) == PCI_DEVICE_3DFX_VOODOO1)) {
tdfx_info->mrdesc.mr_flags = MDF_UNCACHEABLE;
tdfx_info->mrdesc.mr_len = 0x1000;
#ifdef DEBUG
device_printf(dev, "MTRR Set Type Uncacheable %x\n",
(u_int32_t)tdfx_info->mrdesc.mr_base);
#endif
}
#ifdef DEBUG
else {
device_printf(dev, "Couldn't Set MTRR\n");
return 0;
}
#endif
return 0;
}
static int
tdfx_open(struct cdev *dev, int flags, int fmt, struct thread *td)
{
struct tdfx_softc *tdfx_info = dev->si_drv1;
if(tdfx_info->busy != 0) return EBUSY;
#ifdef DEBUG
printf("3dfx: Opened by #%d\n", td->td_proc->p_pid);
#endif
tdfx_info->busy++;
return 0;
}
static int
tdfx_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
{
struct tdfx_softc *tdfx_info = dev->si_drv1;
if(tdfx_info->busy == 0) return EBADF;
tdfx_info->busy = 0;
#ifdef DEBUG
printf("Closed by #%d\n", td->td_proc->p_pid);
#endif
return 0;
}
static int
tdfx_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr,
int nprot, vm_memattr_t *memattr)
{
struct tdfx_softc* tdfx_info[2];
tdfx_info[0] = (struct tdfx_softc*)devclass_get_softc(tdfx_devclass, 0);
if(tdfx_info[0] == NULL) {
#ifdef DEBUG
printf("tdfx: tdfx_info (softc) is NULL\n");
#endif
return -1;
}
if((offset & 0xff000000) == tdfx_info[0]->addr0) {
offset &= 0xffffff;
*paddr = rman_get_start(tdfx_info[0]->memrange) + offset;
return 0;
}
if(tdfx_count > 1) {
tdfx_info[1] = (struct tdfx_softc*)devclass_get_softc(tdfx_devclass, 1);
if((offset & 0xff000000) == tdfx_info[1]->addr0) {
offset &= 0xffffff;
*paddr = rman_get_start(tdfx_info[1]->memrange) +
offset;
return 0;
}
}
return -1;
}
static int
tdfx_query_boards(void) {
return tdfx_count;
}
static int
tdfx_query_fetch(u_int cmd, struct tdfx_pio_data *piod)
{
u_int8_t ret_byte;
u_int16_t ret_word;
u_int32_t ret_dword;
struct tdfx_softc* tdfx_info = NULL;
if((piod == NULL) ||(tdfx_count <= piod->device) ||
(piod->device < 0)) {
#ifdef DEBUG
printf("tdfx: Bad device or internal struct in tdfx_query_fetch\n");
#endif
return -EINVAL;
}
tdfx_info = (struct tdfx_softc*)devclass_get_softc(tdfx_devclass,
piod->device);
if(tdfx_info == NULL) return -ENXIO;
switch(piod->port) {
case PCI_VENDOR_ID_FREEBSD:
if(piod->size != 2) return -EINVAL;
return -copyout(&tdfx_info->vendor, piod->value, piod->size);
case PCI_DEVICE_ID_FREEBSD:
if(piod->size != 2) return -EINVAL;
return -copyout(&tdfx_info->type, piod->value, piod->size);
case PCI_BASE_ADDRESS_0_FREEBSD:
if(piod->size != 4) return -EINVAL;
return -copyout(&tdfx_info->addr0, piod->value, piod->size);
case PCI_BASE_ADDRESS_1_FREEBSD:
if(piod->size != 4) return -EINVAL;
return -copyout(&tdfx_info->addr1, piod->value, piod->size);
case PCI_PRIBUS_FREEBSD:
if(piod->size != 1) return -EINVAL;
break;
case PCI_IOBASE_0_FREEBSD:
if(piod->size != 2) return -EINVAL;
break;
case PCI_IOLIMIT_0_FREEBSD:
if(piod->size != 2) return -EINVAL;
break;
case SST1_PCI_SPECIAL1_FREEBSD:
if(piod->size != 4) return -EINVAL;
break;
case PCI_REVISION_ID_FREEBSD:
if(piod->size != 1) return -EINVAL;
break;
case SST1_PCI_SPECIAL4_FREEBSD:
if(piod->size != 4) return -EINVAL;
break;
default:
return -EINVAL;
}
switch(piod->size) {
case 1:
ret_byte = pci_read_config(tdfx_info[piod->device].dev,
piod->port, 1);
return -copyout(&ret_byte, piod->value, 1);
case 2:
ret_word = pci_read_config(tdfx_info[piod->device].dev,
piod->port, 2);
return -copyout(&ret_word, piod->value, 2);
case 4:
ret_dword = pci_read_config(tdfx_info[piod->device].dev,
piod->port, 4);
return -copyout(&ret_dword, piod->value, 4);
default:
return -EINVAL;
}
}
static int
tdfx_query_update(u_int cmd, struct tdfx_pio_data *piod)
{
u_int8_t ret_byte;
u_int16_t ret_word;
u_int32_t ret_dword;
int error;
u_int32_t retval, preval, mask;
struct tdfx_softc* tdfx_info = NULL;
if((piod == NULL) || (piod->device >= (tdfx_count &
0xf))) {
#ifdef DEBUG
printf("tdfx: Bad struct or device in tdfx_query_update\n");
#endif
return -EINVAL;
}
tdfx_info = (struct tdfx_softc*)devclass_get_softc(tdfx_devclass,
piod->device);
if(tdfx_info == NULL) return -ENXIO;
switch(piod->port) {
case PCI_COMMAND_FREEBSD:
if(piod->size != 2) return -EINVAL;
break;
case SST1_PCI_SPECIAL1_FREEBSD:
if(piod->size != 4) return -EINVAL;
break;
case SST1_PCI_SPECIAL2_FREEBSD:
if(piod->size != 4) return -EINVAL;
break;
case SST1_PCI_SPECIAL3_FREEBSD:
if(piod->size != 4) return -EINVAL;
break;
case SST1_PCI_SPECIAL4_FREEBSD:
if(piod->size != 4) return -EINVAL;
break;
default:
return -EINVAL;
}
retval = pci_read_config(tdfx_info->dev, piod->port & ~3, 4);
switch (piod->size) {
case 1:
error = copyin(piod->value, &ret_byte, 1);
if (error != 0)
return -error;
preval = ret_byte << (8 * (piod->port & 0x3));
mask = 0xff << (8 * (piod->port & 0x3));
break;
case 2:
error = copyin(piod->value, &ret_word, 2);
if (error != 0)
return -error;
preval = ret_word << (8 * (piod->port & 0x3));
mask = 0xffff << (8 * (piod->port & 0x3));
break;
case 4:
error = copyin(piod->value, &ret_dword, 4);
if (error != 0)
return -error;
preval = ret_dword;
mask = ~0;
break;
default:
return -EINVAL;
}
retval = (retval & ~mask) | preval;
pci_write_config(tdfx_info->dev, piod->port & ~3, retval, 4);
return 0;
}
static int
tdfx_do_pio_rd(struct tdfx_pio_data *piod)
{
u_int8_t ret_byte;
u_int workport;
struct tdfx_softc *tdfx_info =
(struct tdfx_softc*)devclass_get_softc(tdfx_devclass, piod->device);
if(((piod->port != VGA_INPUT_STATUS_1C) || (piod->port != SC_INDEX) ||
(piod->port != SC_DATA) || (piod->port != VGA_MISC_OUTPUT_READ)) &&
(piod->port < tdfx_info->pio0) && (piod->port > tdfx_info->pio0max))
return -EPERM;
if(piod->size != 1) {
return -EINVAL;
}
workport = piod->port;
ret_byte = inb(workport);
return copyout(&ret_byte, piod->value, sizeof(u_int8_t));
}
static int
tdfx_do_pio_wt(struct tdfx_pio_data *piod)
{
u_int8_t ret_byte;
u_int workport;
struct tdfx_softc *tdfx_info = (struct
tdfx_softc*)devclass_get_softc(tdfx_devclass, piod->device);
if(((piod->port != SC_INDEX) && (piod->port != SC_DATA) &&
(piod->port != VGA_MISC_OUTPUT_READ)) &&
(piod->port < tdfx_info->pio0) && (piod->port > tdfx_info->pio0max))
return -EPERM;
if(piod->size != 1) {
return -EINVAL;
}
int error = -copyin(piod->value, &ret_byte, sizeof(u_int8_t));
if (error == 0) {
workport = piod->port;
outb(workport, ret_byte);
}
return error;
}
static int
tdfx_do_query(u_int cmd, struct tdfx_pio_data *piod)
{
switch(_IOC_NR(cmd)) {
case 2:
return tdfx_query_boards();
break;
case 3:
return tdfx_query_fetch(cmd, piod);
break;
case 4:
return tdfx_query_update(cmd, piod);
break;
default:
#ifdef DEBUG
printf("Bad Sub-cmd: 0x%x\n", _IOC_NR(cmd));
#endif
return -EINVAL;
}
}
static int
tdfx_do_pio(u_int cmd, struct tdfx_pio_data *piod)
{
switch(_IOC_DIR(cmd)) {
case IOCV_OUT:
return tdfx_do_pio_rd(piod);
break;
case IOCV_IN:
return tdfx_do_pio_wt(piod);
break;
default:
return -EINVAL;
}
}
static int
tdfx_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
{
int retval = 0;
struct tdfx_pio_data *piod = (struct tdfx_pio_data*)data;
#ifdef DEBUG
printf("IOCTL'd by #%d, cmd: 0x%x, data: %p\n", td->td_proc->p_pid, (u_int32_t)cmd,
piod);
#endif
switch(_IOC_TYPE(cmd)) {
case 0x33:
if((retval = tdfx_do_query(cmd, piod)) > 0) td->td_retval[0] = retval;
else return -retval;
break;
case 0:
if((tdfx_do_pio(cmd, piod)) > 0) td->td_retval[0] = retval;
else return -retval;
break;
default:
#ifdef DEBUG
printf("Bad IOCTL from #%d\n", td->td_proc->p_pid);
#endif
return ENXIO;
}
return 0;
}
static int
tdfx_mod_event(module_t mod, int what, void *arg)
{
int error;
switch (what) {
case MOD_LOAD:
tdfx_devclass = devclass_create("tdfx");
error = 0;
break;
case MOD_UNLOAD:
error = 0;
break;
default:
error = EOPNOTSUPP;
break;
}
return (error);
}
static driver_t tdfx_driver = {
"tdfx",
tdfx_methods,
sizeof(struct tdfx_softc),
};
DRIVER_MODULE(tdfx, pci, tdfx_driver, tdfx_mod_event, NULL);
MODULE_DEPEND(tdfx, mem, 1, 1, 1);
MODULE_VERSION(tdfx, 1);