⛏️ index : haiku.git

/*
 * Copyright 2007-2012 Haiku, Inc.  All rights reserved.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *		Gerald Zajac
 */


#include <AGP.h>
#include <KernelExport.h>
#include <PCI.h>
#include <drivers/bios.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <graphic_driver.h>
#include <boot_item.h>

#include "DriverInterface.h"


#undef TRACE

#ifdef ENABLE_DEBUG_TRACE
#	define TRACE(x...) dprintf("i810: " x)
#else
#	define TRACE(x...) ;
#endif


#define ACCELERANT_NAME    "intel_810.accelerant"

#define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1))

#define MAX_DEVICES		4
#define DEVICE_FORMAT	"%04X_%04X_%02X%02X%02X"

#define VENDOR_ID	0x8086	// Intel vendor ID


struct ChipInfo {
	uint16		chipID;		// PCI device id of the chip
	const char*	chipName;	// user recognizable name (must be < 32 chars)
};


// This table maps a PCI device ID to a chip type identifier and the chip name.

static const ChipInfo chipTable[] = {
	{ 0x7121, "i810" },
	{ 0x7123, "i810-dc100" },
	{ 0x7125, "i810e" },
	{ 0x1132, "i815" },
	{ 0, NULL }
};


struct DeviceInfo {
	uint32			openCount;		// how many times device has been opened
	int32			flags;
	area_id 		sharedArea;		// shared between driver and accelerants
	SharedInfo* 	sharedInfo;		// pointer to shared info area memory
	vuint8*	 		regs;			// pointer to memory mapped registers
	const ChipInfo*	pChipInfo;		// info about the selected chip
	pci_info		pciInfo;		// copy of pci info for this device
	area_id			gttArea;		// area used for GTT
	addr_t			gttAddr;		// virtual address of GTT
	char			name[B_OS_NAME_LENGTH]; // name of device
};


static Benaphore		gLock;
static DeviceInfo		gDeviceInfo[MAX_DEVICES];
static char*			gDeviceNames[MAX_DEVICES + 1];
static pci_module_info*	gPCI;


// Prototypes for device hook functions.
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
};


// Video chip register definitions.
// =================================

#define INTERRUPT_ENABLED		0x020a0
#define INTERRUPT_MASK			0x020a8

// Graphics address translation table.
#define PAGE_TABLE_CONTROL		0x02020
#define PAGE_TABLE_ENABLED		0x01

#define PTE_BASE				0x10000
#define PTE_VALID				0x01


// Macros for memory mapped I/O.
// ==============================

#define INREG16(addr)		(*((vuint16*)(di.regs + (addr))))
#define INREG32(addr)		(*((vuint32*)(di.regs + (addr))))

#define OUTREG16(addr, val)	(*((vuint16*)(di.regs + (addr))) = (val))
#define OUTREG32(addr, val)	(*((vuint32*)(di.regs + (addr))) = (val))


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
GetEdidFromBIOS(edid1_raw& edidRaw)
{
	// Get the EDID info from the video BIOS, and return B_OK if successful.

	#define ADDRESS_SEGMENT(address) ((addr_t)(address) >> 4)
	#define ADDRESS_OFFSET(address) ((addr_t)(address) & 0xf)

	bios_module_info* biosModule;
	status_t status = get_module(B_BIOS_MODULE_NAME, (module_info**)&biosModule);
	if (status != B_OK) {
		TRACE("GetEdidFromBIOS(): failed to get BIOS module: 0x%" B_PRIx32 "\n",
			status);
		return status;
	}

	bios_state* state;
	status = biosModule->prepare(&state);
	if (status != B_OK) {
		TRACE("GetEdidFromBIOS(): bios_prepare() failed: 0x%" B_PRIx32 "\n",
			status);
		put_module(B_BIOS_MODULE_NAME);
		return status;
	}

	bios_regs regs = {};
	regs.eax = 0x4f15;
	regs.ebx = 0;			// 0 = report DDC service
	regs.ecx = 0;
	regs.es = 0;
	regs.edi = 0;

	status = biosModule->interrupt(state, 0x10, &regs);
	if (status == B_OK) {
		// AH contains the error code, and AL determines whether or not the
		// function is supported.
		if (regs.eax != 0x4f)
			status = B_NOT_SUPPORTED;

		// Test if DDC is supported by the monitor.
		if ((regs.ebx & 3) == 0)
			status = B_NOT_SUPPORTED;
	}

	if (status == B_OK) {
		edid1_raw* edid = (edid1_raw*)biosModule->allocate_mem(state,
			sizeof(edid1_raw));
		if (edid == NULL) {
			status = B_NO_MEMORY;
			goto out;
		}

		regs.eax = 0x4f15;
		regs.ebx = 1;		// 1 = read EDID
		regs.ecx = 0;
		regs.edx = 0;
		regs.es  = ADDRESS_SEGMENT(edid);
		regs.edi = ADDRESS_OFFSET(edid);

		status = biosModule->interrupt(state, 0x10, &regs);
		if (status == B_OK) {
			if (regs.eax != 0x4f) {
				status = B_NOT_SUPPORTED;
			} else {
				// Copy the EDID info to the caller's location, and compute the
				// checksum of the EDID info while copying.

				uint8 sum = 0;
				uint8 allOr = 0;
				uint8* dest = (uint8*)&edidRaw;
				uint8* src = (uint8*)edid;

				for (uint32 j = 0; j < sizeof(edidRaw); j++) {
					sum += *src;
					allOr |= *src;
					*dest++ = *src++;
				}

				if (allOr == 0) {
					TRACE("GetEdidFromBIOS(); EDID info contains only zeros\n");
					status = B_ERROR;
				} else if (sum != 0) {
					TRACE("GetEdidFromBIOS(); Checksum error in EDID info\n");
					status = B_ERROR;
				}
			}
		}
	}

out:
	biosModule->finish(state);
	put_module(B_BIOS_MODULE_NAME);

	TRACE("GetEdidFromBIOS() status: 0x%" B_PRIx32 "\n", status);
	return status;
}


static status_t
InitDevice(DeviceInfo& di)
{
	// Perform initialization and mapping of the device, and return B_OK if
	// sucessful;  else, return error code.

	TRACE("enter InitDevice()\n");

	// Create the area for shared info with NO user-space read or write
	// permissions, to prevent accidental damage.

	size_t sharedSize = (sizeof(SharedInfo) + 7) & ~7;

	di.sharedArea = create_area("i810 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_USER_CLONEABLE_AREA);
	if (di.sharedArea < 0)
		return di.sharedArea;	// return error code

	SharedInfo& si = *(di.sharedInfo);
	memset(&si, 0, sharedSize);
	si.regsArea = -1;			// indicate area has not yet been created
	si.videoMemArea = -1;

	pci_info& pciInfo = di.pciInfo;

	si.vendorID = pciInfo.vendor_id;
	si.deviceID = pciInfo.device_id;
	si.revision = pciInfo.revision;
	strcpy(si.chipName, di.pChipInfo->chipName);

	// Enable memory mapped IO and bus master.

	SetPCI(pciInfo, PCI_command, 2, GetPCI(pciInfo, PCI_command, 2)
		| PCI_command_io | PCI_command_memory | PCI_command_master);

	// Map the MMIO register area.

	phys_addr_t regsBase = pciInfo.u.h0.base_registers[1];
	uint32 regAreaSize = pciInfo.u.h0.base_register_sizes[1];

	si.regsArea = map_physical_memory("i810 mmio registers",
		regsBase,
		regAreaSize,
		B_ANY_KERNEL_ADDRESS,
		B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_USER_CLONEABLE_AREA,
		(void**)&di.regs);

	if (si.regsArea < 0) {
		TRACE("Unable to map MMIO, error: 0x%lx\n", si.regsArea);
		return si.regsArea;
	}

	// Allocate memory for the GTT which must be 64K for the 810/815 chips.

	uint32 gttSize = 64 * 1024;
	di.gttArea = create_area("GTT memory", (void**) &(di.gttAddr),
		B_ANY_KERNEL_ADDRESS, gttSize, B_FULL_LOCK | B_CONTIGUOUS,
		B_READ_AREA | B_WRITE_AREA);

	if (di.gttArea < B_OK) {
		TRACE("Unable to create GTT, error: 0x%lx\n", di.gttArea);
		return B_NO_MEMORY;
	}

	memset((void*)(di.gttAddr), 0, gttSize);

	// Get the physical address of the GTT, and set GTT address in the chip.

	physical_entry entry;
	status_t status = get_memory_map((void *)(di.gttAddr),
		B_PAGE_SIZE, &entry, 1);
	if (status < B_OK) {
		TRACE("Unable to get physical address of GTT, error: 0x%lx\n", status);
		return status;
	}

	OUTREG32(PAGE_TABLE_CONTROL, entry.address | PAGE_TABLE_ENABLED);
	INREG32(PAGE_TABLE_CONTROL);

	// Allocate video memory to be used for the frame buffer.

	si.videoMemSize = 4 * 1024 * 1024;
	si.videoMemArea = create_area("video memory", (void**)&(si.videoMemAddr),
		B_ANY_ADDRESS, si.videoMemSize, B_FULL_LOCK,
		B_READ_AREA | B_WRITE_AREA);
	if (si.videoMemArea < B_OK) {
		TRACE("Unable to create video memory, error: 0x%lx\n", si.videoMemArea);
		return B_NO_MEMORY;
	}

	// Get the physical address of each page of the video memory, and put
	// the physical address of each page into the GTT table.

	for (uint32 offset = 0; offset < si.videoMemSize; offset += B_PAGE_SIZE) {
		status = get_memory_map((void *)(si.videoMemAddr + offset),
			B_PAGE_SIZE, &entry, 1);
		if (status < B_OK) {
			TRACE("Unable to get physical address of video memory page, error:"
				" 0x%lx  offset: %ld\n", status, offset);
			return status;
		}

		if (offset == 0)
			si.videoMemPCI = entry.address;

		OUTREG32(PTE_BASE + ((offset / B_PAGE_SIZE) * 4),
			entry.address | PTE_VALID);
	}

	TRACE("InitDevice() exit OK\n");
	return B_OK;
}


static void
DeleteAreas(DeviceInfo& di)
{
	// Delete all areas that were created.

	if (di.sharedArea >= 0 && di.sharedInfo != NULL) {
		SharedInfo& si = *(di.sharedInfo);
		if (si.regsArea >= 0)
			delete_area(si.regsArea);
		if (si.videoMemArea >= 0)
			delete_area(si.videoMemArea);
	}

	if (di.gttArea >= 0)
		delete_area(di.gttArea);
	di.gttArea = -1;
	di.gttAddr = (addr_t)NULL;

	if (di.sharedArea >= 0)
		delete_area(di.sharedArea);
	di.sharedArea = -1;
	di.sharedInfo = NULL;
}


static const ChipInfo*
GetNextSupportedDevice(uint32& pciIndex, pci_info& pciInfo)
{
	// Search the PCI devices for a device that is supported by this driver.
	// The search starts at the device specified by argument pciIndex, and
	// continues until a supported device is found or there are no more devices
	// to examine.  Argument pciIndex is incremented after each device is
	// examined.

	// If a supported device is found, return a pointer to the struct containing
	// the chip info; else return NULL.

	while (gPCI->get_nth_pci_info(pciIndex, &pciInfo) == B_OK) {

		if (pciInfo.vendor_id == VENDOR_ID) {

			// Search the table of supported devices to find a chip/device that
			// matches device ID of the current PCI device.

			const ChipInfo* pDevice = chipTable;

			while (pDevice->chipID != 0) {	// end of table?
				if (pDevice->chipID == pciInfo.device_id)
					return pDevice; // matching device/chip found

				pDevice++;
			}
		}

		pciIndex++;
	}

	return NULL; // no supported device found
}


//	#pragma mark - Kernel Interface


status_t
init_hardware(void)
{
	// Return B_OK if a device supported by this driver is found; otherwise,
	// return B_ERROR so the driver will be unloaded.

	status_t status = get_module(B_PCI_MODULE_NAME, (module_info**)&gPCI);
	if (status != B_OK) {
		TRACE("PCI module unavailable, error 0x%lx\n", status);
		return status;
	}

	// Check pci devices for a device supported by this driver.

	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);		// put away the module manager

	return (pDevice == NULL ? B_ERROR : B_OK);
}


status_t
init_driver(void)
{
	// Get handle for the pci bus.

	status_t status = get_module(B_PCI_MODULE_NAME, (module_info**)&gPCI);
	if (status != B_OK) {
		TRACE("PCI module unavailable, error 0x%lx\n", status);
		return status;
	}

	status = gLock.Init("i810 driver lock");
	if (status < B_OK) {
		put_module(B_AGP_GART_MODULE_NAME);
		put_module(B_PCI_MODULE_NAME);
		return status;
	}

	// Get info about all the devices supported by this driver.

	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;			// all supported devices have been obtained

		// Compose device name.
		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;		// mark driver as available for R/W open
		di.sharedArea = -1;		// indicate shared area not yet created
		di.sharedInfo = NULL;
		di.gttArea = -1;		// indicate GTT area not yet created
		di.gttAddr = (addr_t)NULL;
		di.pChipInfo = pDevice;
		count++;
		pciIndex++;
	}

	gDeviceNames[count] = NULL;	// terminate list with null pointer

	TRACE("init_driver() %ld supported devices\n", count);

	return B_OK;
}


void
uninit_driver(void)
{
	// Free the driver data.

	gLock.Delete();
	put_module(B_AGP_GART_MODULE_NAME);
	put_module(B_PCI_MODULE_NAME);	// put the pci module away
}


const char**
publish_devices(void)
{
	return (const char**)gDeviceNames;	// return list of supported devices
}


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;
}


//	#pragma mark - Device Hooks


static status_t
device_open(const char* name, uint32 /*flags*/, void** cookie)
{
	status_t status = B_OK;

	TRACE("device_open() - name: %s, cookie: 0x%" B_PRIXADDR "\n", name,
		(addr_t)cookie);

	// Find the device name in the list of devices.

	int32 i = 0;
	while (gDeviceNames[i] != NULL && (strcmp(name, gDeviceNames[i]) != 0))
		i++;

	if (gDeviceNames[i] == NULL)
		return B_BAD_VALUE;		// device name not found in list of devices

	DeviceInfo& di = gDeviceInfo[i];

	gLock.Acquire();	// make sure no one else has write access to common data

	if (di.openCount == 0) {
		status = InitDevice(di);
		if (status < B_OK)
			DeleteAreas(di);	// error occurred; delete any areas created
	}

	gLock.Release();

	if (status == B_OK) {
		di.openCount++;		// mark device open
		*cookie = &di;		// send cookie to opener
	}

	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)
{
	// Following 3 lines of code are here to eliminate "unused parameter"
	// warnings.
	(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)
{
	// Following 3 lines of code are here to eliminate "unused parameter"
	// warnings.
	(void)dev;
	(void)pos;
	(void)buf;

	*len = 0;
	return B_NOT_ALLOWED;
}


static status_t
device_close(void* dev)
{
	(void)dev;		// avoid compiler warning for unused arg

	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();		// lock driver

	// If opened multiple times, merely decrement the open count and exit.

	if (di.openCount <= 1)
		DeleteAreas(di);

	if (di.openCount > 0)
		di.openCount--;		// mark device available

	gLock.Release();	// unlock driver

	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);

	TRACE("device_ioctl(); ioctl: %lu, buffer: 0x%" B_PRIXADDR ", "
		"bufLen: %lu\n", msg, (addr_t)buffer, bufferLength);

	switch (msg) {
		case B_GET_ACCELERANT_SIGNATURE:
			strcpy((char*)buffer, ACCELERANT_NAME);
			TRACE("Intel 810 accelerant: %s\n", ACCELERANT_NAME);
			return B_OK;

		case INTEL_DEVICE_NAME:
			strncpy((char*)buffer, di.name, B_OS_NAME_LENGTH);
			((char*)buffer)[B_OS_NAME_LENGTH -1] = '\0';
			return B_OK;

		case INTEL_GET_SHARED_DATA:
			if (bufferLength != sizeof(area_id))
				return B_BAD_DATA;

			*((area_id*)buffer) = di.sharedArea;
			return B_OK;

		case INTEL_GET_EDID:
		{
			if (bufferLength != sizeof(edid1_raw))
				return B_BAD_DATA;

			edid1_raw rawEdid;
			status_t status = GetEdidFromBIOS(rawEdid);
			if (status == B_OK)
				user_memcpy((edid1_raw*)buffer, &rawEdid, sizeof(rawEdid));
			return status;
		}
	}

	return B_DEV_INVALID_IOCTL;
}