/* Copyright 2007-2011 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 "DriverInterface.h" #undef TRACE #ifdef ENABLE_DEBUG_TRACE # define TRACE(x...) dprintf("ati: " x) #else # define TRACE(x...) ; #endif #define ATI_ACCELERANT_NAME "ati.accelerant" #define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1)) #define VESA_MODES_BOOT_INFO "vesa_modes/v1" #define SKD_HANDLER_INSTALLED 0x80000000 #define MAX_DEVICES 4 #define DEVICE_FORMAT "%04X_%04X_%02X%02X%02X" #define M64_BIOS_SIZE 0x10000 // 64KB #define R128_BIOS_SIZE 0x10000 // 64KB int32 api_version = B_CUR_DRIVER_API_VERSION; // revision of driver API used #define VENDOR_ID 0x1002 // ATI vendor ID // Mach64 register definitions. #define M64_CLOCK_INTERNAL 4 #define M64_CONFIG_CHIP_ID 0x0CE0 // offset in register area #define M64_CFG_CHIP_TYPE 0x0000FFFF struct ChipInfo { uint16 chipID; // PCI device id of the chip ChipType chipType; // assigned chip type identifier const char* chipName; // user recognizable name for chip // (must be < 32 chars) }; // Names for chip types. static char sRage128_GL[] = "RAGE 128 GL"; static char sRage128_VR[] = "RAGE 128 VR"; static char sRage128_Pro_GL[] = "RAGE 128 PRO GL"; static char sRage128_Pro_VR[] = "RAGE 128 PRO VR"; static char sRage128_Pro_Ultra[] = "RAGE 128 PRO Ultra"; // This table maps a PCI device ID to a chip type identifier and the chip name. // The table is split into two groups of chips, the Mach64 and Rage128 chips, // with each group ordered by the chip ID. static const ChipInfo chipTable[] = { { 0x4742, MACH64_264GTPRO, "3D RAGE PRO, AGP" }, // GB { 0x4744, MACH64_264GTPRO, "3D RAGE PRO, AGP" }, // GD { 0x4749, MACH64_264GTPRO, "3D RAGE PRO, PCI" }, // GI { 0x474C, MACH64_264XL, "3D RAGE XC, PCI" }, // GL { 0x474D, MACH64_264XL, "3D RAGE XL, AGP" }, // GM { 0x474E, MACH64_264XL, "3D RAGE XC, AGP" }, // GN { 0x474F, MACH64_264XL, "3D RAGE XL, PCI" }, // GO { 0x4750, MACH64_264GTPRO, "3D RAGE PRO, PCI" }, // GP { 0x4751, MACH64_264GTPRO, "3D RAGE PRO, PCI" }, // GQ { 0x4752, MACH64_264XL, "3D RAGE XL, PCI" }, // GR { 0x4753, MACH64_264XL, "3D RAGE XC, PCI" }, // GS { 0x4754, MACH64_264GT, "3D RAGE II" }, // GT { 0x4755, MACH64_264GTDVD, "3D RAGE II+" }, // GU { 0x4756, MACH64_264GT2C, "3D RAGE IIC, PCI" }, // GV { 0x4757, MACH64_264GT2C, "3D RAGE IIC, AGP" }, // GW { 0x4759, MACH64_264GT2C, "3D RAGE IIC, PCI" }, // GY { 0x475A, MACH64_264GT2C, "3D RAGE IIC, AGP" }, // GZ { 0x4C42, MACH64_264LTPRO, "3D RAGE LT PRO, AGP" }, // LB { 0x4C44, MACH64_264LTPRO, "3D RAGE LT PRO, AGP" }, // LD { 0x4C47, MACH64_264LT, "3D RAGE LT" }, // LG { 0x4C49, MACH64_264LTPRO, "3D RAGE LT PRO, PCI" }, // LI { 0x4C4D, MACH64_MOBILITY, "3D RAGE Mobility, AGP" }, // LM { 0x4C4E, MACH64_MOBILITY, "3D RAGE Mobility, AGP" }, // LN { 0x4C50, MACH64_264LTPRO, "3D RAGE LT PRO, PCI" }, // LP { 0x4C51, MACH64_264LTPRO, "3D RAGE LT PRO, PCI" }, // LQ { 0x4C52, MACH64_MOBILITY, "3D RAGE Mobility, PCI" }, // LR { 0x4C53, MACH64_MOBILITY, "3D RAGE Mobility, PCI" }, // LS { 0x5654, MACH64_264VT, "264VT2" }, // VT { 0x5655, MACH64_264VT3, "264VT3" }, // VU { 0x5656, MACH64_264VT4, "264VT4" }, // VV { 0x4C45, RAGE128_MOBILITY, "RAGE 128 Mobility 3" }, // LE { 0x4C46, RAGE128_MOBILITY, "RAGE 128 Mobility 3" }, // LF { 0x4D46, RAGE128_MOBILITY, "RAGE 128 Mobility 4" }, // MF { 0x4D4C, RAGE128_MOBILITY, "RAGE 128 Mobility 4" }, // ML { 0x5041, RAGE128_PRO_GL, sRage128_Pro_GL }, // PA { 0x5042, RAGE128_PRO_GL, sRage128_Pro_GL }, // PB { 0x5043, RAGE128_PRO_GL, sRage128_Pro_GL }, // PC { 0x5044, RAGE128_PRO_GL, sRage128_Pro_GL }, // PD { 0x5045, RAGE128_PRO_GL, sRage128_Pro_GL }, // PE { 0x5046, RAGE128_PRO_GL, sRage128_Pro_GL }, // PF { 0x5047, RAGE128_PRO_VR, sRage128_Pro_VR }, // PG { 0x5048, RAGE128_PRO_VR, sRage128_Pro_VR }, // PH { 0x5049, RAGE128_PRO_VR, sRage128_Pro_VR }, // PI { 0x504A, RAGE128_PRO_VR, sRage128_Pro_VR }, // PJ { 0x504B, RAGE128_PRO_VR, sRage128_Pro_VR }, // PK { 0x504C, RAGE128_PRO_VR, sRage128_Pro_VR }, // PL { 0x504D, RAGE128_PRO_VR, sRage128_Pro_VR }, // PM { 0x504E, RAGE128_PRO_VR, sRage128_Pro_VR }, // PN { 0x504F, RAGE128_PRO_VR, sRage128_Pro_VR }, // PO { 0x5050, RAGE128_PRO_VR, sRage128_Pro_VR }, // PP { 0x5051, RAGE128_PRO_VR, sRage128_Pro_VR }, // PQ { 0x5052, RAGE128_PRO_VR, sRage128_Pro_VR }, // PR { 0x5053, RAGE128_PRO_VR, sRage128_Pro_VR }, // PS { 0x5054, RAGE128_PRO_VR, sRage128_Pro_VR }, // PT { 0x5055, RAGE128_PRO_VR, sRage128_Pro_VR }, // PU { 0x5056, RAGE128_PRO_VR, sRage128_Pro_VR }, // PV { 0x5057, RAGE128_PRO_VR, sRage128_Pro_VR }, // PW { 0x5058, RAGE128_PRO_VR, sRage128_Pro_VR }, // PX { 0x5245, RAGE128_GL, sRage128_GL }, // RE { 0x5246, RAGE128_GL, sRage128_GL }, // RF { 0x5247, RAGE128_GL, sRage128_GL }, // RG { 0x524B, RAGE128_VR, sRage128_VR }, // RK { 0x524C, RAGE128_VR, sRage128_VR }, // RL { 0x5345, RAGE128_VR, sRage128_VR }, // SE { 0x5346, RAGE128_VR, sRage128_VR }, // SF { 0x5347, RAGE128_VR, sRage128_VR }, // SG { 0x5348, RAGE128_VR, sRage128_VR }, // SH { 0x534B, RAGE128_GL, sRage128_GL }, // SK { 0x534C, RAGE128_GL, sRage128_GL }, // SL { 0x534D, RAGE128_GL, sRage128_GL }, // SM { 0x534E, RAGE128_GL, sRage128_GL }, // SN { 0x5446, RAGE128_PRO_ULTRA, sRage128_Pro_Ultra }, // TF { 0x544C, RAGE128_PRO_ULTRA, sRage128_Pro_Ultra }, // TL { 0x5452, RAGE128_PRO_ULTRA, sRage128_Pro_Ultra }, // TR { 0x5453, RAGE128_PRO_ULTRA, sRage128_Pro_Ultra }, // TS { 0x5454, RAGE128_PRO_ULTRA, sRage128_Pro_Ultra }, // TT { 0x5455, RAGE128_PRO_ULTRA, sRage128_Pro_Ultra }, // TU { 0, ATI_NONE, NULL } }; struct DeviceInfo { uint32 openCount; // count of how many times device has been opened int32 flags; area_id sharedArea; // area shared between driver and all 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 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 }; 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); } // Functions for dealing with Vertical Blanking Interrupts. Currently, I do // not know the commands to handle these operations; thus, these functions // currently do nothing. static bool InterruptIsVBI() { // return true only if a vertical blanking interrupt has occured return false; } static void ClearVBI() { } static void EnableVBI() { } static void DisableVBI() { } 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); return status; } static status_t SetVesaDisplayMode(uint16 mode) { // Set the VESA display mode, and return B_OK if successful. #define SET_MODE_MASK 0x01ff #define SET_MODE_LINEAR_BUFFER (1 << 14) bios_module_info* biosModule; status_t status = get_module(B_BIOS_MODULE_NAME, (module_info**)&biosModule); if (status != B_OK) { TRACE("SetVesaDisplayMode(0x%x): failed to get BIOS module: 0x%" B_PRIx32 "\n", mode, status); return status; } bios_state* state; status = biosModule->prepare(&state); if (status != B_OK) { TRACE("SetVesaDisplayMode(0x%x): bios_prepare() failed: 0x%" B_PRIx32 "\n", mode, status); put_module(B_BIOS_MODULE_NAME); return status; } bios_regs regs = {}; regs.eax = 0x4f02; regs.ebx = (mode & SET_MODE_MASK) | SET_MODE_LINEAR_BUFFER; status = biosModule->interrupt(state, 0x10, ®s); if (status != B_OK) { TRACE("SetVesaDisplayMode(0x%x): BIOS interrupt failed\n", mode); } if (status == B_OK && (regs.eax & 0xffff) != 0x4f) { TRACE("SetVesaDisplayMode(0x%x): BIOS returned 0x%04" B_PRIx32 "\n", mode, regs.eax & 0xffff); status = B_ERROR; } biosModule->finish(state); put_module(B_BIOS_MODULE_NAME); return status; } // Macros for accessing BIOS info. #define BIOS8(v) (romAddr[v]) #define BIOS16(v) (romAddr[v] | \ (romAddr[(v) + 1] << 8)) #define BIOS32(v) (romAddr[v] | \ (romAddr[(v) + 1] << 8) | \ (romAddr[(v) + 2] << 16) | \ (romAddr[(v) + 3] << 24)) static status_t Mach64_GetBiosParameters(DeviceInfo& di, uint8& clockType) { // Get some clock parameters from the video BIOS, and if Mobility chip, // also get the LCD panel width & height. // In case mapping the ROM area fails or other error occurs, set default // values for the parameters which will be obtained from the BIOS ROM. clockType = M64_CLOCK_INTERNAL; SharedInfo& si = *(di.sharedInfo); M64_Params& params = si.m64Params; params.clockNumberToProgram = 3; si.panelX = 0; si.panelY = 0; // Map the ROM area. The Mach64 chips do not assign a ROM address in the // PCI info; thus, access the ROM via the ISA legacy memory map. uint8* romAddr; area_id romArea = map_physical_memory("ATI Mach64 ROM", #if defined(__x86__) || defined(__x86_64__) 0x000c0000, #else di.pciInfo.u.h0.rom_base, #endif M64_BIOS_SIZE, B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA, (void**)&(romAddr)); if (romArea < 0) { TRACE("Mach64_GetBiosParameters(), ROM mapping error: %" B_PRId32 "\n", romArea); return romArea; // ROM mapping failed; return error code } // Check if we have the BIOS signature (might fail on laptops..). if (BIOS8(0) != 0x55 || BIOS8(1) != 0xaa) { TRACE("Mach64_GetBiosParameters(), ROM does not contain BIOS signature\n"); delete_area(romArea); return B_ERROR; } // Get clock info from BIOS. uint32 romTable = BIOS16(0x48); uint32 clockTable = BIOS16(romTable + 16); clockType = BIOS8(clockTable); params.clockNumberToProgram = BIOS8(clockTable + 6); params.maxPixelClock = BIOS16(clockTable + 4) * 10; params.refFreq = BIOS16(clockTable + 8); params.refDivider = BIOS16(clockTable + 10); // If Mobility chip, get the LCD panel width & height. if (si.chipType == MACH64_MOBILITY) { uint32 lcdTable = BIOS16(0x78); if (BIOS32(lcdTable) == 0x544d5224) { // is LCD table signature correct? uint32 lcdPanelInfo = BIOS16(lcdTable + 10); si.panelX = BIOS16(lcdPanelInfo + 25); si.panelY = BIOS16(lcdPanelInfo + 27); TRACE("Mobility LCD Panel size: %dx%d\n", si.panelX, si.panelY); } else { TRACE("Mobility LCD table signature 0x%x in BIOS is incorrect\n", BIOS32(lcdTable)); } } delete_area(romArea); return B_OK; } static status_t Rage128_GetBiosParameters(DeviceInfo& di) { // Get the PLL parameters from the video BIOS, and if Mobility chips, also // get the LCD panel width & height and a few other related parameters. // In case mapping the ROM area fails or other error occurs, set default // values for the parameters which will be obtained from the BIOS ROM. // The default PLL parameters values probably will not work for all chips. // For example, reference freq can be 29.50MHz, 28.63MHz, or 14.32MHz. SharedInfo& si = *(di.sharedInfo); R128_PLLParams& pll = si.r128PLLParams; pll.reference_freq = 2950; pll.reference_div = 65; pll.min_pll_freq = 12500; pll.max_pll_freq = 25000; pll.xclk = 10300; si.panelX = 0; si.panelY = 0; si.panelPowerDelay = 1; // Map the ROM area. The Rage128 chips do not assign a ROM address in the // PCI info; thus, access the ROM via the ISA legacy memory map. uint8* romAddr; area_id romArea = map_physical_memory("ATI Rage128 ROM", #if defined(__x86__) || defined(__x86_64__) 0x000c0000, #else di.pciInfo.u.h0.rom_base, #endif R128_BIOS_SIZE, B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA, (void**)&(romAddr)); if (romArea < 0) { TRACE("Rage128_GetBiosParameters(), ROM mapping error: %" B_PRId32 "\n", romArea); return romArea; // ROM mapping failed; return error code } // Check if we got the BIOS signature (might fail on laptops..). if (BIOS8(0) != 0x55 || BIOS8(1) != 0xaa) { TRACE("Rage128_GetBiosParameters(), ROM does not contain BIOS signature\n"); delete_area(romArea); return B_ERROR; } // Get the PLL values from the mapped ROM area. uint16 biosHeader = BIOS16(0x48); uint16 pllInfoBlock = BIOS16(biosHeader + 0x30); pll.reference_freq = BIOS16(pllInfoBlock + 0x0e); pll.reference_div = BIOS16(pllInfoBlock + 0x10); pll.min_pll_freq = BIOS32(pllInfoBlock + 0x12); pll.max_pll_freq = BIOS32(pllInfoBlock + 0x16); pll.xclk = BIOS16(pllInfoBlock + 0x08); TRACE("PLL parameters: rf=%d rd=%d min=%" B_PRId32 " max=%" B_PRId32 "; xclk=%d\n", pll.reference_freq, pll.reference_div, pll.min_pll_freq, pll.max_pll_freq, pll.xclk); // If Mobility chip, get the LCD panel width & height and a few other // related parameters. if (si.chipType == RAGE128_MOBILITY) { // There should be direct access to the start of the FP info table, but // until we find out where that offset is stored, we must search for // the ATI signature string: "M3 ". int i; for (i = 4; i < R128_BIOS_SIZE - 8; i++) { if (BIOS8(i) == 'M' && BIOS8(i + 1) == '3' && BIOS8(i + 2) == ' ' && BIOS8(i + 3) == ' ' && BIOS8(i + 4) == ' ' && BIOS8(i + 5) == ' ' && BIOS8(i + 6) == ' ' && BIOS8(i + 7) == ' ') { int fpHeader = i - 2; // Assume that only one panel is attached and supported. for (i = fpHeader + 20; i < fpHeader + 84; i += 2) { if (BIOS16(i) != 0) { int fpStart = BIOS16(i); si.panelX = BIOS16(fpStart + 25); si.panelY = BIOS16(fpStart + 27); si.panelPowerDelay = BIOS8(fpStart + 56); TRACE("LCD Panel size: %dx%d Panel type: 0x%x power delay: %d\n", si.panelX, si.panelY, BIOS16(fpStart + 29), si.panelPowerDelay); break; } } break; } } } delete_area(romArea); return B_OK; } static status_t MapDevice(DeviceInfo& di) { SharedInfo& si = *(di.sharedInfo); pci_info& pciInfo = di.pciInfo; // 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); // Enable ROM decoding if (di.pciInfo.u.h0.rom_size > 0) { SetPCI(pciInfo, PCI_rom_base, 4, GetPCI(pciInfo, PCI_rom_base, 4) | 0x00000001); } // Map the video memory. phys_addr_t videoRamAddr = pciInfo.u.h0.base_registers[0]; uint32 videoRamSize = pciInfo.u.h0.base_register_sizes[0]; si.videoMemPCI = videoRamAddr; char frameBufferAreaName[] = "ATI 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)); if (si.videoMemArea < 0) { // Try to map this time without write combining. 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; // Map the MMIO register area. phys_addr_t regsBase = pciInfo.u.h0.base_registers[2]; uint32 regAreaSize = pciInfo.u.h0.base_register_sizes[2]; // If the register area address or size is not in the PCI info, it should // be at the end of the video memory. Check if it is there. if (MACH64_FAMILY(si.chipType) && (regsBase == 0 || regAreaSize == 0)) { uint32 regsOffset = 0x7ff000; // offset to regs area in video memory addr_t regs = addr_t(si.videoMemAddr) + regsOffset; uint32 chipInfo = *((vuint32*)(regs + M64_CONFIG_CHIP_ID)); if (si.deviceID != (chipInfo & M64_CFG_CHIP_TYPE)) { // Register area not found; delete any other areas that were // created. delete_area(si.videoMemArea); si.videoMemArea = -1; TRACE("Mach64 register area not found\n"); return B_ERROR; } // Adjust params for creating register area below. regsBase = videoRamAddr + regsOffset; regAreaSize = 0x1000; TRACE("Register address is at end of frame buffer memory at 0x%" B_PRIxPHYSADDR "\n", regsBase); } si.regsArea = map_physical_memory("ATI mmio registers", regsBase, regAreaSize, B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_CLONEABLE_AREA, (void**)&di.regs); // If there was an error, delete other areas. if (si.regsArea < 0) { delete_area(si.videoMemArea); si.videoMemArea = -1; } 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 int32 InterruptHandler(void* data) { int32 handled = B_UNHANDLED_INTERRUPT; DeviceInfo& di = *((DeviceInfo*)data); int32* flags = &(di.flags); // Is someone already handling an interrupt for this device? if (atomic_or(flags, SKD_HANDLER_INSTALLED) & SKD_HANDLER_INSTALLED) return B_UNHANDLED_INTERRUPT; if (InterruptIsVBI()) { // was interrupt a VBI? ClearVBI(); // clear interrupt handled = B_HANDLED_INTERRUPT; // Release vertical blanking semaphore. sem_id& sem = di.sharedInfo->vertBlankSem; if (sem >= 0) { int32 blocked; if ((get_sem_count(sem, &blocked) == B_OK) && (blocked < 0)) { release_sem_etc(sem, -blocked, B_DO_NOT_RESCHEDULE); handled = B_INVOKE_SCHEDULER; } } } atomic_and(flags, ~SKD_HANDLER_INSTALLED); // note we're not in handler anymore return handled; } static void InitInterruptHandler(DeviceInfo& di) { SharedInfo& si = *(di.sharedInfo); DisableVBI(); // disable & clear any pending interrupts si.bInterruptAssigned = false; // indicate interrupt not assigned yet // Create a semaphore for vertical blank management. si.vertBlankSem = create_sem(0, di.name); if (si.vertBlankSem < 0) return; // Change the owner of the semaphores to the calling team (usually the // app_server). This is required because apps can't aquire kernel // semaphores. thread_id threadID = find_thread(NULL); thread_info threadInfo; status_t status = get_thread_info(threadID, &threadInfo); if (status == B_OK) status = set_sem_owner(si.vertBlankSem, threadInfo.team); // If there is a valid interrupt assigned, set up interrupts. if (status == B_OK && di.pciInfo.u.h0.interrupt_pin != 0x00 && di.pciInfo.u.h0.interrupt_line != 0xff) { // We have a interrupt line to use. status = install_io_interrupt_handler(di.pciInfo.u.h0.interrupt_line, InterruptHandler, (void*)(&di), 0); if (status == B_OK) si.bInterruptAssigned = true; // we can use interrupt related functions } if (status != B_OK) { // Interrupt does not exist; thus delete semaphore as it won't be used. delete_sem(si.vertBlankSem); si.vertBlankSem = -1; } } static status_t InitDevice(DeviceInfo& di) { // Perform initialization and mapping of the device, and return B_OK if // sucessful; else, return error code. // Get the table of VESA modes that the chip supports. Note that we will // need this table only for chips that are currently connected to a laptop // display or a monitor connected via a DVI interface. size_t vesaModeTableSize = 0; VesaMode* vesaModes = (VesaMode*)get_boot_item(VESA_MODES_BOOT_INFO, &vesaModeTableSize); size_t sharedSize = (sizeof(SharedInfo) + 7) & ~7; // Create the area for shared info with NO user-space read or write // permissions, to prevent accidental damage. di.sharedArea = create_area("ATI shared info", (void**) &(di.sharedInfo), B_ANY_KERNEL_ADDRESS, ROUND_TO_PAGE_SIZE(sharedSize + vesaModeTableSize), B_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_CLONEABLE_AREA); if (di.sharedArea < 0) return di.sharedArea; // return error code SharedInfo& si = *(di.sharedInfo); memset(&si, 0, sharedSize); if (vesaModes != NULL) { si.vesaModeTableOffset = sharedSize; si.vesaModeCount = vesaModeTableSize / sizeof(VesaMode); memcpy((uint8*)&si + si.vesaModeTableOffset, vesaModes, vesaModeTableSize); } 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); TRACE("Chip revision: 0x%x\n", si.revision); // 264GT has two chip versions. If version is non-zero, chip is 264GTB. if (si.chipType == MACH64_264GT && si.revision & 0x7) si.chipType = MACH64_264GTB; // 264VT has two chip versions. If version is non-zero, chip is 264VTB. if (si.chipType == MACH64_264VT && si.revision & 0x7) si.chipType = MACH64_264VTB; status_t status = MapDevice(di); // If device mapped without any error, get the bios parameters from the // chip's BIOS ROM. if (status >= 0) { if (MACH64_FAMILY(si.chipType)) { uint8 clockType; Mach64_GetBiosParameters(di, clockType); // All chips supported by this driver should have an internal clock. // If the clock is not an internal clock, the video chip is not // supported. if (clockType != M64_CLOCK_INTERNAL) { TRACE("Video chip clock type %d not supported\n", clockType); status = B_UNSUPPORTED; } } else if (RAGE128_FAMILY(si.chipType)) Rage128_GetBiosParameters(di); } if (status < 0) { delete_area(di.sharedArea); di.sharedArea = -1; di.sharedInfo = NULL; return status; // return error code } InitInterruptHandler(di); TRACE("Interrupt assigned: %s\n", si.bInterruptAssigned ? "yes" : "no"); return B_OK; } 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) { // Matching device/chip found. If chip is 264VT, reject it // if its version is zero since the mode can not be set on // that chip. if (pDevice->chipType == MACH64_264VT && (pciInfo.revision & 0x7) == 0) break; 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. if (get_module(B_PCI_MODULE_NAME, (module_info**)&gPCI) != B_OK) return B_ERROR; // unable to access PCI bus // 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. if (get_module(B_PCI_MODULE_NAME, (module_info**)&gPCI) != B_OK) return B_ERROR; status_t status = gLock.Init("ATI driver lock"); if (status < B_OK) 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.pChipInfo = pDevice; count++; pciIndex++; } gDeviceNames[count] = NULL; // terminate list with null pointer TRACE("init_driver() %" B_PRIu32 " supported devices\n", count); return B_OK; } void uninit_driver(void) { // Free the driver data. gLock.Delete(); 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\n", name); // 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); gLock.Release(); if (status == B_OK) { di.openCount++; // mark device open *cookie = &di; // send cookie to opener } TRACE("device_open() returning 0x%" B_PRIx32 ", open count: %" B_PRIu32 "\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 return B_NO_ERROR; } static status_t device_free(void* dev) { DeviceInfo& di = *((DeviceInfo*)dev); SharedInfo& si = *(di.sharedInfo); pci_info& pciInfo = di.pciInfo; TRACE("enter device_free()\n"); gLock.Acquire(); // lock driver // If opened multiple times, merely decrement the open count and exit. if (di.openCount <= 1) { DisableVBI(); // disable & clear any pending interrupts if (si.bInterruptAssigned) { remove_io_interrupt_handler(pciInfo.u.h0.interrupt_line, InterruptHandler, &di); } // Delete the semaphores, ignoring any errors because the owning team // may have died. if (si.vertBlankSem >= 0) delete_sem(si.vertBlankSem); si.vertBlankSem = -1; UnmapDevice(di); // free regs and frame buffer areas delete_area(di.sharedArea); di.sharedArea = -1; di.sharedInfo = NULL; } if (di.openCount > 0) di.openCount--; // mark device available gLock.Release(); // unlock driver TRACE("exit device_free() openCount: %" B_PRIu32 "\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: %" B_PRIu32 ", buffer: %#08" B_PRIxADDR ", bufLen: %" B_PRIuSIZE "\n", msg, (addr_t)buffer, bufferLength); switch (msg) { case B_GET_ACCELERANT_SIGNATURE: { status_t status = user_strlcpy((char*)buffer, ATI_ACCELERANT_NAME, bufferLength); if (status < B_OK) return status; return B_OK; } case ATI_DEVICE_NAME: { status_t status = user_strlcpy((char*)buffer, di.name, B_OS_NAME_LENGTH); if (status < B_OK) return status; return B_OK; } case ATI_GET_SHARED_DATA: if (bufferLength != sizeof(area_id)) return B_BAD_DATA; return user_memcpy(buffer, &di.sharedArea, sizeof(area_id)); case ATI_GET_EDID: { if (bufferLength != sizeof(edid1_raw)) return B_BAD_DATA; edid1_raw rawEdid; status_t status = GetEdidFromBIOS(rawEdid); if (status != B_OK) return status; return user_memcpy((edid1_raw*)buffer, &rawEdid, sizeof(rawEdid)); } case ATI_SET_VESA_DISPLAY_MODE: { if (bufferLength != sizeof(uint16)) return B_BAD_DATA; uint16 value; status_t status = user_memcpy(&value, buffer, sizeof(uint16)); if (status < B_OK) return status; return SetVesaDisplayMode(value); } case ATI_RUN_INTERRUPTS: { if (bufferLength != sizeof(bool)) return B_BAD_DATA; bool value; status_t res = user_memcpy(&value, buffer, sizeof(bool)); if (res < B_OK) return res; if (value) EnableVBI(); else DisableVBI(); return B_OK; } } return B_DEV_INVALID_IOCTL; }