/*- * Copyright (c) 2000-2001 by Coleman Kane * 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 Gardner Buchanan. * 4. The name of Gardner Buchanan 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$"); /* 3dfx driver for FreeBSD 4.x - Finished 11 May 2000, 12:25AM ET * * Copyright (C) 2000-2001, by Coleman Kane , * based upon the 3dfx driver written for linux, by Daryll Straus, Jon Taylor, * and Jens Axboe, located at http://linux.3dfx.com. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* rman.h depends on machine/bus.h */ #include #include #include /* This must come first */ #include "opt_tdfx.h" #ifdef TDFX_LINUX #include #endif #include #include #include static devclass_t tdfx_devclass; static int tdfx_count = 0; /* Set up the boot probe/attach routines */ 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), { 0, 0 } }; MALLOC_DEFINE(M_TDFX,"tdfx_driver","3DFX Graphics[/2D]/3D Accelerator(s)"); #ifdef TDFX_LINUX MODULE_DEPEND(tdfx, linux, 1, 1, 1); LINUX_IOCTL_SET(tdfx, LINUX_IOCTL_TDFX_MIN, LINUX_IOCTL_TDFX_MAX); #endif /* Char. Dev. file operations structure */ 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) { /* * probe routine called on kernel boot to register supported devices. We get * a device structure to work with, and we can test the VENDOR/DEVICE IDs to * see if this PCI device is one that we support. Return BUS_PRROBE_DEFAULT * if yes, ENXIO if not. */ 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_BANSHEE: device_set_desc(dev, "3DFX Voodoo Banshee 2D/3D Graphics Accelerator"); return BUS_PROBE_DEFAULT; case PCI_DEVICE_3DFX_VOODOO3: device_set_desc(dev, "3DFX Voodoo3 2D/3D Graphics 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) { /* * The attach routine is called after the probe routine successfully says it * supports a given card. We now proceed to initialize this card for use with * the system. I want to map the device memory for userland allocation and * fill an information structure with information on this card. I'd also like * to set Write Combining with the MTRR code so that we can hopefully speed * up memory writes. The last thing is to register the character device * interface to the card, so we can open it from /dev/3dfxN, where N is a * small, whole number. */ struct tdfx_softc *tdfx_info; u_long val; /* rid value tells bus_alloc_resource where to find the addresses of ports or * of memory ranges in the PCI config space*/ int rid = PCIR_BAR(0); /* Increment the card counter (for the ioctl code) */ tdfx_count++; /* Enable MemMap on Voodoo */ val = pci_read_config(dev, PCIR_COMMAND, 2); val |= (PCIM_CMD_MEMEN); pci_write_config(dev, PCIR_COMMAND, val, 2); val = pci_read_config(dev, PCIR_COMMAND, 2); /* Fill the soft config struct with info about this device*/ 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; /* * Get the Memory Location from the PCI Config, mask out lower word, since * the config space register is only one word long (this is nicer than a * bitshift). */ tdfx_info->addr0 = (pci_read_config(dev, 0x10, 4) & 0xffff0000); #ifdef DEBUG device_printf(dev, "Base0 @ 0x%x\n", tdfx_info->addr0); #endif /* Notify the VM that we will be mapping some memory later */ 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 } /* Setup for Voodoo3 and Banshee, PIO and an extram Memrange */ if(pci_get_devid(dev) == PCI_DEVICE_3DFX_VOODOO3 || pci_get_devid(dev) == PCI_DEVICE_3DFX_BANSHEE) { rid = 0x14; /* 2nd mem map */ 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; } /* Now to map the PIO stuff */ 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; } /* * Set Writecombining, or at least Uncacheable for the memory region, if we * are able to */ if(tdfx_setmtrr(dev) != 0) { #ifdef DEBUG device_printf(dev, "Some weird error setting MTRRs"); #endif return -1; } /* * make_dev registers the cdev to access the 3dfx card from /dev * use hex here for the dev num, simply to provide better support if > 10 * voodoo cards, for the mad. The user must set the link, or use MAKEDEV. * Why would we want that many voodoo cards anyhow? */ tdfx_info->devt = make_dev(&tdfx_cdev, device_get_unit(dev), UID_ROOT, GID_WHEEL, 0600, "3dfx%x", device_get_unit(dev)); return 0; } static int tdfx_detach(device_t dev) { struct tdfx_softc* tdfx_info; int retval; tdfx_info = device_get_softc(dev); /* Delete allocated resource, of course */ bus_release_resource(dev, SYS_RES_MEMORY, tdfx_info->memrid, tdfx_info->memrange); /* Release extended Voodoo3/Banshee resources */ 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); /* if(tdfx_info->piorange != NULL) bus_release_resource(dev, SYS_RES_IOPORT, tdfx_info->piorid, tdfx_info->piorange);*/ } /* Though it is safe to leave the WRCOMB support since the mem driver checks for it, we should remove it in order to free an MTRR for another device */ retval = tdfx_clrmtrr(dev); #ifdef DEBUG if(retval != 0) printf("tdfx: For some reason, I couldn't clear the mtrr\n"); #endif /* Remove device entry when it can no longer be accessed */ 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) { /* This function removes the MTRR set by the attach call, so it can be used * in the future by other drivers. */ 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) { /* * This is the MTRR setting function for the 3dfx card. It is called from * tdfx_attach. If we can't set the MTRR properly, it's not the end of the * world. We can still continue, just with slightly (very slightly) degraded * performance. */ int retval = 0, act; struct tdfx_softc *tdfx_info = device_get_softc(dev); /* The older Voodoo cards have a shorter memrange than the newer ones */ if((pci_get_devid(dev) == PCI_DEVICE_3DFX_VOODOO1) || (pci_get_devid(dev) == PCI_DEVICE_3DFX_VOODOO2)) { tdfx_info->mrdesc.mr_len = 0x400000; /* The memory descriptor is described as the top 15 bits of the real address */ 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; /* The Voodoo3 and Banshee LFB is the second memory address */ /* The memory descriptor is described as the top 15 bits of the real address */ tdfx_info->mrdesc.mr_base = tdfx_info->addr1 & 0xfffe0000; } else return 0; /* * The Alliance Pro Motion AT3D was not mentioned in the linux * driver as far as MTRR support goes, so I just won't put the * code in here for it. This is where it should go, though. */ /* Firstly, try to set write combining */ 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)) { /* if, for some reason we can't set the WRCOMB range with the V1/V2, we * can still possibly use the UNCACHEABLE region for it instead, and help * out in a small way */ tdfx_info->mrdesc.mr_flags = MDF_UNCACHEABLE; /* This length of 1000h was taken from the linux device driver... */ tdfx_info->mrdesc.mr_len = 0x1000; /* * If, for some reason, we can't set the MTRR (N/A?) we may still continue */ #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) { /* * The open cdev method handles open(2) calls to /dev/3dfx[n] * We can pretty much allow any opening of the device. */ struct tdfx_softc *tdfx_info = devclass_get_softc(tdfx_devclass, UNIT(minor(dev))); if(tdfx_info->busy != 0) return EBUSY; #ifdef DEBUG printf("3dfx: Opened by #%d\n", td->td_proc->p_pid); #endif /* Set the driver as busy */ tdfx_info->busy++; return 0; } static int tdfx_close(struct cdev *dev, int fflag, int devtype, struct thread *td) { /* * The close cdev method handles close(2) calls to /dev/3dfx[n] * We'll always want to close the device when it's called. */ struct tdfx_softc *tdfx_info = devclass_get_softc(tdfx_devclass, UNIT(minor(dev))); 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_offset_t offset, vm_paddr_t *paddr, int nprot) { /* * mmap(2) is called by a user process to request that an area of memory * associated with this device be mapped for the process to work with. Nprot * holds the protections requested, PROT_READ, PROT_WRITE, or both. */ /**** OLD GET CONFIG ****/ /* struct tdfx_softc* tdfx_info; */ /* Get the configuration for our card XXX*/ /*tdfx_info = (struct tdfx_softc*)devclass_get_softc(tdfx_devclass, UNIT(minor(dev)));*/ /************************/ struct tdfx_softc* tdfx_info[2]; tdfx_info[0] = (struct tdfx_softc*)devclass_get_softc(tdfx_devclass, 0); /* If, for some reason, its not configured, we bail out */ if(tdfx_info[0] == NULL) { #ifdef DEBUG printf("tdfx: tdfx_info (softc) is NULL\n"); #endif return -1; } /* We must stay within the bound of our address space */ 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; } } /* See if the Banshee/V3 LFB is being requested */ /*if(tdfx_info->memrange2 != NULL && (offset & 0xff000000) == tdfx_info->addr1) { offset &= 0xffffff; return atop(rman_get_start(tdfx_info[1]->memrange2) + offset); }*/ /* VoodooNG code */ /* The ret call */ /* atop -> address to page * rman_get_start, get the (struct resource*)->r_start member, * the mapping base address. */ return -1; } static int tdfx_query_boards(void) { /* * This returns the number of installed tdfx cards, we have been keeping * count, look at tdfx_attach */ return tdfx_count; } static int tdfx_query_fetch(u_int cmd, struct tdfx_pio_data *piod) { /* XXX Comment this later, after careful inspection and spring cleaning :) */ /* Various return values 8bit-32bit */ u_int8_t ret_byte; u_int16_t ret_word; u_int32_t ret_dword; struct tdfx_softc* tdfx_info = NULL; /* This one depend on the tdfx_* structs being properly initialized */ /*piod->device &= 0xf;*/ 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; /* We must restrict the size reads from the port, since to high or low of a * size witll result in wrong data being passed, and that's bad */ /* A few of these were pulled during the attach phase */ switch(piod->port) { case PCI_VENDOR_ID_FREEBSD: if(piod->size != 2) return -EINVAL; copyout(&tdfx_info->vendor, piod->value, piod->size); return 0; case PCI_DEVICE_ID_FREEBSD: if(piod->size != 2) return -EINVAL; copyout(&tdfx_info->type, piod->value, piod->size); return 0; case PCI_BASE_ADDRESS_0_FREEBSD: if(piod->size != 4) return -EINVAL; copyout(&tdfx_info->addr0, piod->value, piod->size); return 0; case PCI_BASE_ADDRESS_1_FREEBSD: if(piod->size != 4) return -EINVAL; copyout(&tdfx_info->addr1, piod->value, piod->size); return 0; 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; } /* Read the value and return */ switch(piod->size) { case 1: ret_byte = pci_read_config(tdfx_info[piod->device].dev, piod->port, 1); copyout(&ret_byte, piod->value, 1); break; case 2: ret_word = pci_read_config(tdfx_info[piod->device].dev, piod->port, 2); copyout(&ret_word, piod->value, 2); break; case 4: ret_dword = pci_read_config(tdfx_info[piod->device].dev, piod->port, 4); copyout(&ret_dword, piod->value, 4); break; default: return -EINVAL; } return 0; } static int tdfx_query_update(u_int cmd, struct tdfx_pio_data *piod) { /* XXX Comment this later, after careful inspection and spring cleaning :) */ /* Return vals */ u_int8_t ret_byte; u_int16_t ret_word; u_int32_t ret_dword; /* Port vals, mask */ 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; /* Code below this line in the fuction was taken from the * Linux driver and converted for freebsd. */ /* Check the size for all the ports, to make sure stuff doesn't get messed up * by poorly written clients */ 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; } /* Read the current value */ retval = pci_read_config(tdfx_info->dev, piod->port & ~3, 4); /* These set up a mask to use, since apparently they wanted to write 4 bytes * at once to the ports */ switch (piod->size) { case 1: copyin(piod->value, &ret_byte, 1); preval = ret_byte << (8 * (piod->port & 0x3)); mask = 0xff << (8 * (piod->port & 0x3)); break; case 2: copyin(piod->value, &ret_word, 2); preval = ret_word << (8 * (piod->port & 0x3)); mask = 0xffff << (8 * (piod->port & 0x3)); break; case 4: copyin(piod->value, &ret_dword, 4); preval = ret_dword; mask = ~0; break; default: return -EINVAL; } /* Finally, combine the values and write it to the port */ retval = (retval & ~mask) | preval; pci_write_config(tdfx_info->dev, piod->port & ~3, retval, 4); return 0; } /* For both of these, I added a variable named workport of type u_int so * that I could eliminate the warning about my data type size. The * applications expect the port to be of type short, so I needed to change * this within the function */ static int tdfx_do_pio_rd(struct tdfx_pio_data *piod) { /* Return val */ u_int8_t ret_byte; u_int workport; struct tdfx_softc *tdfx_info = (struct tdfx_softc*)devclass_get_softc(tdfx_devclass, piod->device); /* Restricts the access of ports other than those we use */ 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; /* All VGA STATUS REGS are byte registers, size should never be > 1 */ if(piod->size != 1) { return -EINVAL; } /* Write the data to the intended port */ workport = piod->port; ret_byte = inb(workport); copyout(&ret_byte, piod->value, sizeof(u_int8_t)); return 0; } static int tdfx_do_pio_wt(struct tdfx_pio_data *piod) { /* return val */ u_int8_t ret_byte; u_int workport; struct tdfx_softc *tdfx_info = (struct tdfx_softc*)devclass_get_softc(tdfx_devclass, piod->device); /* Replace old switch w/ massive if(...) */ /* Restricts the access of ports other than those we use */ if(((piod->port != SC_INDEX) && (piod->port != SC_DATA) && (piod->port != VGA_MISC_OUTPUT_READ)) /* Can't write VGA_ST_1C */ && (piod->port < tdfx_info->pio0) && (piod->port > tdfx_info->pio0max)) return -EPERM; /* All VGA STATUS REGS are byte registers, size should never be > 1 */ if(piod->size != 1) { return -EINVAL; } /* Write the data to the intended port */ copyin(piod->value, &ret_byte, sizeof(u_int8_t)); workport = piod->port; outb(workport, ret_byte); return 0; } static int tdfx_do_query(u_int cmd, struct tdfx_pio_data *piod) { /* There are three sub-commands to the query 0x33 */ 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: /* In case we are thrown a bogus sub-command! */ #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) { /* Two types of PIO, INPUT and OUTPUT, as the name suggests */ 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; } } /* Calls to ioctl(2) eventually end up here. Unhandled ioctls return an ENXIO, * normally, you would read in the data pointed to by data, then write your * output to it. The ioctl *should* normally return zero if everything is * alright, but 3dfx didn't make it that way... * * For all of the ioctl code, in the event of a real error, * we return -Exxxx rather than simply Exxxx. The reason for this * is that the ioctls actually RET information back to the program * sometimes, rather than filling it in the passed structure. We * want to distinguish errors from useful data, and maintain compatibility. * * There is this portion of the proc struct called p_retval[], we can store a * return value in td->td_retval[0] and place the return value if it is positive * in there, then we can return 0 (good). If the return value is negative, we * can return -retval and the error should be properly handled. */ 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)) { /* Return the real error if negative, or simply stick the valid return * in td->td_retval */ case 0x33: /* The '3'(0x33) type IOCTL is for querying the installed cards */ if((retval = tdfx_do_query(cmd, piod)) > 0) td->td_retval[0] = retval; else return -retval; break; case 0: /* The 0 type IOCTL is for programmed I/O methods */ if((tdfx_do_pio(cmd, piod)) > 0) td->td_retval[0] = retval; else return -retval; break; default: /* Technically, we won't reach this from linux emu, but when glide * finally gets ported, watch out! */ #ifdef DEBUG printf("Bad IOCTL from #%d\n", td->td_proc->p_pid); #endif return ENXIO; } return 0; } #ifdef TDFX_LINUX /* * Linux emulation IOCTL for /dev/tdfx */ static int linux_ioctl_tdfx(struct thread *td, struct linux_ioctl_args* args) { int error = 0; u_long cmd = args->cmd & 0xffff; /* The structure passed to ioctl has two shorts, one int and one void*. */ char d_pio[2*sizeof(short) + sizeof(int) + sizeof(void*)]; struct file *fp; if ((error = fget(td, args->fd, &fp)) != 0) return (error); /* We simply copy the data and send it right to ioctl */ copyin((caddr_t)args->arg, &d_pio, sizeof(d_pio)); error = fo_ioctl(fp, cmd, (caddr_t)&d_pio, td->td_ucred, td); fdrop(fp, td); return error; } #endif /* TDFX_LINUX */ /* This is the device driver struct. This is sent to the driver subsystem to * register the method structure and the info strcut space for this particular * instance of the driver. */ static driver_t tdfx_driver = { "tdfx", tdfx_methods, sizeof(struct tdfx_softc), }; /* Tell Mr. Kernel about us! */ DRIVER_MODULE(tdfx, pci, tdfx_driver, tdfx_devclass, 0, 0);