/* * Copyright 2007-2012 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT license. * * Authors: * Gerald Zajac */ #include #include #include #include #include #include #include #include #include #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, ®s); 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, ®s); 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; }