* Copyright 2007-2010 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT license.
*
* Authors:
* Gerald Zajac
*/
#include <KernelExport.h>
#include <PCI.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <graphic_driver.h>
#ifdef __HAIKU__
#include <boot_item.h>
#endif
#include "DriverInterface.h"
#undef TRACE
#ifdef ENABLE_DEBUG_TRACE
# define TRACE(x...) dprintf("3dfx: " x)
#else
# define TRACE(x...) ;
#endif
#define ACCELERANT_NAME "3dfx.accelerant"
#define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1))
#define SKD_HANDLER_INSTALLED 0x80000000
#define MAX_DEVICES 4
#define DEVICE_FORMAT "%04X_%04X_%02X%02X%02X"
int32 api_version = B_CUR_DRIVER_API_VERSION;
#define VENDOR_ID 0x121A // 3DFX vendor ID
struct ChipInfo {
uint16 chipID;
ChipType chipType;
const char* chipName;
};
static const ChipInfo chipTable[] = {
{ 0x03, BANSHEE, "Banshee" },
{ 0x05, VOODOO_3, "Voodoo 3" },
{ 0x09, VOODOO_5, "Voodoo 5" },
{ 0, TDFX_NONE, NULL }
};
struct DeviceInfo {
uint32 openCount;
int32 flags;
area_id sharedArea;
SharedInfo* sharedInfo;
vuint8* regs;
const ChipInfo* pChipInfo;
pci_info pciInfo;
char name[B_OS_NAME_LENGTH];
};
static Benaphore gLock;
static DeviceInfo gDeviceInfo[MAX_DEVICES];
static char* gDeviceNames[MAX_DEVICES + 1];
static pci_module_info* gPCI;
static status_t device_open(const char* name, uint32 flags, void** cookie);
static status_t device_close(void* dev);
static status_t device_free(void* dev);
static status_t device_read(void* dev, off_t pos, void* buf, size_t* len);
static status_t device_write(void* dev, off_t pos, const void* buf,
size_t* len);
static status_t device_ioctl(void* dev, uint32 msg, void* buf, size_t len);
static device_hooks gDeviceHooks =
{
device_open,
device_close,
device_free,
device_ioctl,
device_read,
device_write,
NULL,
NULL,
NULL,
NULL
};
static inline uint32
GetPCI(pci_info& info, uint8 offset, uint8 size)
{
return gPCI->read_pci_config(info.bus, info.device, info.function, offset,
size);
}
static inline void
SetPCI(pci_info& info, uint8 offset, uint8 size, uint32 value)
{
gPCI->write_pci_config(info.bus, info.device, info.function, offset, size,
value);
}
static status_t
MapDevice(DeviceInfo& di)
{
SharedInfo& si = *(di.sharedInfo);
pci_info& pciInfo = di.pciInfo;
TRACE("enter MapDevice()\n");
SetPCI(pciInfo, PCI_command, 2, GetPCI(pciInfo, PCI_command, 2)
| PCI_command_io | PCI_command_memory | PCI_command_master);
phys_addr_t videoRamAddr = pciInfo.u.h0.base_registers[1];
uint32 videoRamSize = pciInfo.u.h0.base_register_sizes[1];
si.videoMemPCI = videoRamAddr;
char frameBufferAreaName[] = "3DFX frame buffer";
si.videoMemArea = map_physical_memory(
frameBufferAreaName,
videoRamAddr,
videoRamSize,
B_ANY_KERNEL_BLOCK_ADDRESS | B_MTR_WC,
B_READ_AREA + B_WRITE_AREA,
(void**)&si.videoMemAddr);
TRACE("Video memory, area: %ld, addr: 0x%lX, size: %ld\n",
si.videoMemArea, (uint32)(si.videoMemAddr), videoRamSize);
if (si.videoMemArea < 0) {
si.videoMemArea = map_physical_memory(
frameBufferAreaName,
videoRamAddr,
videoRamSize,
B_ANY_KERNEL_BLOCK_ADDRESS,
B_READ_AREA + B_WRITE_AREA,
(void**)&si.videoMemAddr);
}
if (si.videoMemArea < 0)
return si.videoMemArea;
phys_addr_t regsBase = pciInfo.u.h0.base_registers[0];
uint32 regAreaSize = pciInfo.u.h0.base_register_sizes[0];
si.regsArea = map_physical_memory("3DFX mmio registers",
regsBase,
regAreaSize,
B_ANY_KERNEL_ADDRESS,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_CLONEABLE_AREA,
(void**)&di.regs);
if (si.regsArea < 0) {
delete_area(si.videoMemArea);
si.videoMemArea = -1;
}
TRACE("leave MapDevice(); result: %ld\n", si.regsArea);
return si.regsArea;
}
static void
UnmapDevice(DeviceInfo& di)
{
SharedInfo& si = *(di.sharedInfo);
if (si.regsArea >= 0)
delete_area(si.regsArea);
if (si.videoMemArea >= 0)
delete_area(si.videoMemArea);
si.regsArea = si.videoMemArea = -1;
si.videoMemAddr = (addr_t)NULL;
di.regs = NULL;
}
static status_t
InitDevice(DeviceInfo& di)
{
TRACE("enter InitDevice()\n");
size_t sharedSize = (sizeof(SharedInfo) + 7) & ~7;
di.sharedArea = create_area("3DFX shared info",
(void**) &(di.sharedInfo),
B_ANY_KERNEL_ADDRESS,
ROUND_TO_PAGE_SIZE(sharedSize),
B_FULL_LOCK,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_CLONEABLE_AREA);
if (di.sharedArea < 0)
return di.sharedArea;
SharedInfo& si = *(di.sharedInfo);
memset(&si, 0, sharedSize);
pci_info& pciInfo = di.pciInfo;
si.vendorID = pciInfo.vendor_id;
si.deviceID = pciInfo.device_id;
si.revision = pciInfo.revision;
si.chipType = di.pChipInfo->chipType;
strcpy(si.chipName, di.pChipInfo->chipName);
status_t status = MapDevice(di);
if (status < 0) {
delete_area(di.sharedArea);
di.sharedArea = -1;
di.sharedInfo = NULL;
return status;
}
return B_OK;
}
static const ChipInfo*
GetNextSupportedDevice(uint32& pciIndex, pci_info& pciInfo)
{
while (gPCI->get_nth_pci_info(pciIndex, &pciInfo) == B_OK) {
if (pciInfo.vendor_id == VENDOR_ID) {
const ChipInfo* pDevice = chipTable;
while (pDevice->chipID != 0) {
if (pDevice->chipID == pciInfo.device_id)
return pDevice;
pDevice++;
}
}
pciIndex++;
}
return NULL;
}
status_t
init_hardware(void)
{
if (get_module(B_PCI_MODULE_NAME, (module_info**)&gPCI) != B_OK)
return B_ERROR;
uint32 pciIndex = 0;
pci_info pciInfo;
const ChipInfo* pDevice = GetNextSupportedDevice(pciIndex, pciInfo);
TRACE("init_hardware() - %s\n",
pDevice == NULL ? "no supported devices" : "device supported");
put_module(B_PCI_MODULE_NAME);
return (pDevice == NULL ? B_ERROR : B_OK);
}
status_t
init_driver(void)
{
if (get_module(B_PCI_MODULE_NAME, (module_info**)&gPCI) != B_OK)
return B_ERROR;
status_t status = gLock.Init("3DFX driver lock");
if (status < B_OK)
return status;
uint32 pciIndex = 0;
uint32 count = 0;
while (count < MAX_DEVICES) {
DeviceInfo& di = gDeviceInfo[count];
const ChipInfo* pDevice = GetNextSupportedDevice(pciIndex, di.pciInfo);
if (pDevice == NULL)
break;
sprintf(di.name, "graphics/" DEVICE_FORMAT,
di.pciInfo.vendor_id, di.pciInfo.device_id,
di.pciInfo.bus, di.pciInfo.device, di.pciInfo.function);
TRACE("init_driver() match found; name: %s\n", di.name);
gDeviceNames[count] = di.name;
di.openCount = 0;
di.sharedArea = -1;
di.sharedInfo = NULL;
di.pChipInfo = pDevice;
count++;
pciIndex++;
}
gDeviceNames[count] = NULL;
TRACE("init_driver() %ld supported devices\n", count);
return B_OK;
}
void
uninit_driver(void)
{
gLock.Delete();
put_module(B_PCI_MODULE_NAME);
}
const char**
publish_devices(void)
{
return (const char**)gDeviceNames;
}
device_hooks*
find_device(const char* name)
{
int i = 0;
while (gDeviceNames[i] != NULL) {
if (strcmp(name, gDeviceNames[i]) == 0)
return &gDeviceHooks;
i++;
}
return NULL;
}
static status_t
device_open(const char* name, uint32 , void** cookie)
{
status_t status = B_OK;
TRACE("device_open() - name: %s, cookie: 0x%08lx)\n", name, (uint32)cookie);
int32 i = 0;
while (gDeviceNames[i] != NULL && (strcmp(name, gDeviceNames[i]) != 0))
i++;
if (gDeviceNames[i] == NULL)
return B_BAD_VALUE;
DeviceInfo& di = gDeviceInfo[i];
gLock.Acquire();
if (di.openCount == 0)
status = InitDevice(di);
gLock.Release();
if (status == B_OK) {
di.openCount++;
*cookie = &di;
}
TRACE("device_open() returning 0x%lx, open count: %ld\n", status,
di.openCount);
return status;
}
static status_t
device_read(void* dev, off_t pos, void* buf, size_t* len)
{
(void)dev;
(void)pos;
(void)buf;
*len = 0;
return B_NOT_ALLOWED;
}
static status_t
device_write(void* dev, off_t pos, const void* buf, size_t* len)
{
(void)dev;
(void)pos;
(void)buf;
*len = 0;
return B_NOT_ALLOWED;
}
static status_t
device_close(void* dev)
{
(void)dev;
TRACE("device_close()\n");
return B_NO_ERROR;
}
static status_t
device_free(void* dev)
{
DeviceInfo& di = *((DeviceInfo*)dev);
TRACE("enter device_free()\n");
gLock.Acquire();
if (di.openCount <= 1) {
UnmapDevice(di);
delete_area(di.sharedArea);
di.sharedArea = -1;
di.sharedInfo = NULL;
}
if (di.openCount > 0)
di.openCount--;
gLock.Release();
TRACE("exit device_free() openCount: %ld\n", di.openCount);
return B_OK;
}
static status_t
device_ioctl(void* dev, uint32 msg, void* buffer, size_t bufferLength)
{
DeviceInfo& di = *((DeviceInfo*)dev);
#ifndef __HAIKU__
(void)bufferLength;
#endif
switch (msg) {
case B_GET_ACCELERANT_SIGNATURE:
strcpy((char*)buffer, ACCELERANT_NAME);
return B_OK;
case TDFX_DEVICE_NAME:
strncpy((char*)buffer, di.name, B_OS_NAME_LENGTH);
((char*)buffer)[B_OS_NAME_LENGTH -1] = '\0';
return B_OK;
case TDFX_GET_SHARED_DATA:
#ifdef __HAIKU__
if (bufferLength != sizeof(area_id))
return B_BAD_DATA;
#endif
*((area_id*)buffer) = di.sharedArea;
return B_OK;
case TDFX_GET_PIO_REG:
{
#ifdef __HAIKU__
if (bufferLength != sizeof(PIORegInfo))
return B_BAD_DATA;
#endif
PIORegInfo* regInfo = (PIORegInfo*)buffer;
if (regInfo->magic == TDFX_PRIVATE_DATA_MAGIC) {
int ioAddr = di.pciInfo.u.h0.base_registers[2] + regInfo->offset;
if (regInfo->index >= 0) {
gPCI->write_io_8(ioAddr, regInfo->index);
regInfo->value = gPCI->read_io_8(ioAddr + 1);
} else {
regInfo->value = gPCI->read_io_8(ioAddr);
}
return B_OK;
}
break;
}
case TDFX_SET_PIO_REG:
{
#ifdef __HAIKU__
if (bufferLength != sizeof(PIORegInfo))
return B_BAD_DATA;
#endif
PIORegInfo* regInfo = (PIORegInfo*)buffer;
if (regInfo->magic == TDFX_PRIVATE_DATA_MAGIC) {
int ioAddr = di.pciInfo.u.h0.base_registers[2] + regInfo->offset;
if (regInfo->index >= 0) {
gPCI->write_io_8(ioAddr, regInfo->index);
gPCI->write_io_8(ioAddr + 1, regInfo->value);
} else {
gPCI->write_io_8(ioAddr, regInfo->value);
}
return B_OK;
}
break;
}
}
return B_DEV_INVALID_IOCTL;
}