intel_extreme: vblank interrupt support for Gen8+

Change-Id: I8e7e68786cc4a626cb386929600715a6a6b1917d
Reviewed-on: https://review.haiku-os.org/c/haiku/+/4760
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: Axel Dörfler <[email protected]>
Reviewed-by: Adrien Destugues <[email protected]>
This commit is contained in:
Kacper Kasper
2022-01-01 11:36:02 +00:00
committed by Adrien Destugues
parent 6117e0c29c
commit fd876ad749
2 changed files with 280 additions and 49 deletions
@@ -341,6 +341,40 @@ enum pipe_index {
INTEL_PIPE_D INTEL_PIPE_D
}; };
class pipes {
public:
pipes() : bitmask(0) {}
bool HasPipe(pipe_index pipe)
{
if (pipe == INTEL_PIPE_ANY)
return bitmask != 0;
return (bitmask & (1 << pipe)) != 0;
}
void SetPipe(pipe_index pipe)
{
if (pipe == INTEL_PIPE_ANY) {
bitmask = ~1;
// first bit corresponds to INTEL_PIPE_ANY but it's never used,
// so it should be 0
}
bitmask |= (1 << pipe);
}
void ClearPipe(pipe_index pipe)
{
if (pipe == INTEL_PIPE_ANY)
bitmask = 0;
bitmask &= ~(1 << pipe);
}
private:
uint8 bitmask;
};
//----------------- ioctl() interface ---------------- //----------------- ioctl() interface ----------------
// magic code for ioctls // magic code for ioctls
@@ -554,6 +588,18 @@ struct intel_free_graphics_memory {
#define PCH_INTERRUPT_VBLANK_PIPEB_SNB (1 << 15) #define PCH_INTERRUPT_VBLANK_PIPEB_SNB (1 << 15)
#define PCH_INTERRUPT_GLOBAL_SNB (1 << 31) #define PCH_INTERRUPT_GLOBAL_SNB (1 << 31)
#define PCH_MASTER_INT_CTL_BDW 0x44200
#define PCH_MASTER_INT_CTL_PIPE_PENDING_BDW(pipe) (1 << (15 + pipe))
#define PCH_MASTER_INT_CTL_GLOBAL_BDW (1 << 31)
#define PCH_INTERRUPT_PIPE_STATUS_BDW(pipe) (0x44400 + (pipe - 1) * 0x10)
#define PCH_INTERRUPT_PIPE_MASK_BDW(pipe) (0x44404 + (pipe - 1) * 0x10)
#define PCH_INTERRUPT_PIPE_IDENTITY_BDW(pipe) (0x44408 + (pipe - 1) * 0x10)
#define PCH_INTERRUPT_PIPE_ENABLED_BDW(pipe) (0x4440c + (pipe - 1) * 0x10)
#define PCH_INTERRUPT_VBLANK_BDW (1 << 0)
// graphics port control (i.e. G45) // graphics port control (i.e. G45)
#define DISPLAY_MONITOR_PORT_ENABLED (1UL << 31) #define DISPLAY_MONITOR_PORT_ENABLED (1UL << 31)
#define DISPLAY_MONITOR_PIPE_B (1UL << 30) #define DISPLAY_MONITOR_PIPE_B (1UL << 30)
@@ -84,18 +84,94 @@ release_vblank_sem(intel_info &info)
} }
static void
bdw_enable_interrupts(intel_info& info, pipe_index pipe, bool enable)
{
ASSERT(pipe != INTEL_PIPE_ANY);
ASSERT(info.device_type.Generation() >= 12 || pipe != INTEL_PIPE_D);
const uint32 regMask = PCH_INTERRUPT_PIPE_MASK_BDW(pipe);
const uint32 regEnabled = PCH_INTERRUPT_PIPE_ENABLED_BDW(pipe);
const uint32 regIdentity = PCH_INTERRUPT_PIPE_IDENTITY_BDW(pipe);
const uint32 value = enable ? PCH_INTERRUPT_VBLANK_BDW : 0;
write32(info, regIdentity, ~0);
write32(info, regEnabled, value);
write32(info, regMask, ~value);
}
static void
bdw_enable_global_interrupts(intel_info& info, bool enable)
{
const uint32 bit = PCH_MASTER_INT_CTL_GLOBAL_BDW;
const uint32 mask = enable ? bit : 0;
const uint32 value = read32(info, PCH_MASTER_INT_CTL_BDW);
write32(info, PCH_MASTER_INT_CTL_BDW, (value & ~bit) | mask);
}
/*!
Checks interrupt status in master interrupt control register.
For Gen8 to Gen11. From Gen11 the register is called DISPLAY_INT_CTL.
*/
static bool
bdw_check_interrupt(intel_info& info, pipes& which)
{
ASSERT(info.device_type.Generation() >= 12 || !which.HasPipe(INTEL_PIPE_D));
which.ClearPipe(INTEL_PIPE_ANY);
const uint32 interrupt = read32(info, PCH_MASTER_INT_CTL_BDW);
if ((interrupt & PCH_MASTER_INT_CTL_PIPE_PENDING_BDW(INTEL_PIPE_A)) != 0)
which.SetPipe(INTEL_PIPE_A);
if ((interrupt & PCH_MASTER_INT_CTL_PIPE_PENDING_BDW(INTEL_PIPE_B)) != 0)
which.SetPipe(INTEL_PIPE_B);
if ((interrupt & PCH_MASTER_INT_CTL_PIPE_PENDING_BDW(INTEL_PIPE_C)) != 0)
which.SetPipe(INTEL_PIPE_C);
if ((interrupt & PCH_MASTER_INT_CTL_PIPE_PENDING_BDW(INTEL_PIPE_D)) != 0)
which.SetPipe(INTEL_PIPE_D);
return which.HasPipe(INTEL_PIPE_ANY);
}
/*!
Checks vblank interrupt status for a specified pipe.
Gen8 to Gen12.
*/
static bool
bdw_check_pipe_interrupt(intel_info& info, pipe_index pipe)
{
ASSERT(pipe != INTEL_PIPE_ANY);
ASSERT(info.device_type.Generation() >= 12 || pipe != INTEL_PIPE_D);
const uint32 identity = read32(info, PCH_INTERRUPT_PIPE_IDENTITY_BDW(pipe));
return (identity & PCH_INTERRUPT_VBLANK_BDW) != 0;
}
static void
bdw_clear_pipe_interrupt(intel_info& info, pipe_index pipe)
{
ASSERT(pipe != INTEL_PIPE_ANY);
ASSERT(info.device_type.Generation() >= 12 || pipe != INTEL_PIPE_D);
const uint32 regIdentity = PCH_INTERRUPT_PIPE_IDENTITY_BDW(pipe);
const uint32 identity = read32(info, regIdentity);
write32(info, regIdentity, identity | PCH_INTERRUPT_VBLANK_BDW);
}
/** Get the appropriate interrupt mask for enabling or testing interrupts on /** Get the appropriate interrupt mask for enabling or testing interrupts on
* the given pipes. * the given pipe.
* *
* The bits to test or set are different depending on the hardware generation. * The bits to test or set are different depending on the hardware generation.
* *
* \param info Intel_extreme driver information * \param info Intel_extreme driver information
* \param pipes bit mask of the pipes to use * \param pipe pipe to use
* \param enable true to get the mask for enabling the interrupts, false to get * \param enable true to get the mask for enabling the interrupts, false to get
* the mask for testing them. * the mask for testing them.
*/ */
static uint32 static uint32
intel_get_interrupt_mask(intel_info& info, int pipes, bool enable) intel_get_interrupt_mask(intel_info& info, pipe_index pipe, bool enable)
{ {
uint32 mask = 0; uint32 mask = 0;
bool hasPCH = info.pch_info != INTEL_PCH_NONE; bool hasPCH = info.pch_info != INTEL_PCH_NONE;
@@ -105,7 +181,7 @@ intel_get_interrupt_mask(intel_info& info, int pipes, bool enable)
// The PCH register itself does not exist in pre-PCH platforms, and the // The PCH register itself does not exist in pre-PCH platforms, and the
// previous interrupt register of course also had a different mapping. // previous interrupt register of course also had a different mapping.
if ((pipes & INTEL_PIPE_A) != 0) { if (pipe == INTEL_PIPE_A) {
if (info.device_type.InGroup(INTEL_GROUP_SNB) if (info.device_type.InGroup(INTEL_GROUP_SNB)
|| info.device_type.InGroup(INTEL_GROUP_ILK)) || info.device_type.InGroup(INTEL_GROUP_ILK))
mask |= PCH_INTERRUPT_VBLANK_PIPEA_SNB; mask |= PCH_INTERRUPT_VBLANK_PIPEA_SNB;
@@ -115,7 +191,7 @@ intel_get_interrupt_mask(intel_info& info, int pipes, bool enable)
mask |= INTERRUPT_VBLANK_PIPEA; mask |= INTERRUPT_VBLANK_PIPEA;
} }
if ((pipes & INTEL_PIPE_B) != 0) { if (pipe == INTEL_PIPE_B) {
if (info.device_type.InGroup(INTEL_GROUP_SNB) if (info.device_type.InGroup(INTEL_GROUP_SNB)
|| info.device_type.InGroup(INTEL_GROUP_ILK)) || info.device_type.InGroup(INTEL_GROUP_ILK))
mask |= PCH_INTERRUPT_VBLANK_PIPEB_SNB; mask |= PCH_INTERRUPT_VBLANK_PIPEB_SNB;
@@ -126,7 +202,7 @@ intel_get_interrupt_mask(intel_info& info, int pipes, bool enable)
} }
#if 0 // FIXME enable when we support the 3rd pipe #if 0 // FIXME enable when we support the 3rd pipe
if ((pipes & INTEL_PIPE_C) != 0) { if (pipe == INTEL_PIPE_C) {
// Older generations only had two pipes // Older generations only had two pipes
if (hasPCH && info.device_type.Generation() > 6) if (hasPCH && info.device_type.Generation() > 6)
mask |= PCH_INTERRUPT_VBLANK_PIPEC; mask |= PCH_INTERRUPT_VBLANK_PIPEC;
@@ -142,58 +218,164 @@ intel_get_interrupt_mask(intel_info& info, int pipes, bool enable)
} }
static void
intel_enable_interrupts(intel_info& info, pipes which, bool enable)
{
uint32 finalMask = 0;
const uint32 pipeAMask = intel_get_interrupt_mask(info, INTEL_PIPE_A, true);
const uint32 pipeBMask = intel_get_interrupt_mask(info, INTEL_PIPE_B, true);
if (which.HasPipe(INTEL_PIPE_A))
finalMask |= pipeAMask;
if (which.HasPipe(INTEL_PIPE_B))
finalMask |= pipeBMask;
const uint32 value = enable ? finalMask : 0;
// Clear all the interrupts
write32(info, find_reg(info, INTEL_INTERRUPT_IDENTITY), ~0);
// enable interrupts - we only want VBLANK interrupts
write32(info, find_reg(info, INTEL_INTERRUPT_ENABLED), value);
write32(info, find_reg(info, INTEL_INTERRUPT_MASK), ~value);
}
static bool
intel_check_interrupt(intel_info& info, pipes& which)
{
which.ClearPipe(INTEL_PIPE_ANY);
const uint32 pipeAMask = intel_get_interrupt_mask(info, INTEL_PIPE_A, false);
const uint32 pipeBMask = intel_get_interrupt_mask(info, INTEL_PIPE_B, false);
const uint32 regIdentity = find_reg(info, INTEL_INTERRUPT_IDENTITY);
const uint32 interrupt = read32(info, regIdentity);
if ((interrupt & pipeAMask) != 0)
which.SetPipe(INTEL_PIPE_A);
if ((interrupt & pipeBMask) != 0)
which.SetPipe(INTEL_PIPE_B);
return which.HasPipe(INTEL_PIPE_ANY);
}
static void
g35_clear_interrupt_status(intel_info& info, pipe_index pipe)
{
// These registers do not exist on later GPUs.
if (info.device_type.Generation() > 4)
return;
const uint32 value = DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED;
switch (pipe) {
case INTEL_PIPE_A:
write32(info, INTEL_DISPLAY_A_PIPE_STATUS, value);
break;
case INTEL_PIPE_B:
write32(info, INTEL_DISPLAY_B_PIPE_STATUS, value);
break;
default:
break;
}
}
static void
intel_clear_pipe_interrupt(intel_info& info, pipe_index pipe)
{
// On G35/G45, prior to clearing Display Pipe interrupt in IIR
// the corresponding interrupt status must first be cleared.
g35_clear_interrupt_status(info, pipe);
const uint32 regIdentity = find_reg(info, INTEL_INTERRUPT_IDENTITY);
const uint32 bit = intel_get_interrupt_mask(info, pipe, false);
const uint32 identity = read32(info, regIdentity);
write32(info, regIdentity, identity | bit);
}
/*!
Interrupt routine for Gen8 and Gen9.
TODO: Gen11 and 12 require one additional check at the beginning.
See Gen12 Display Engine: Interrupt Service Routine chapter.
*/
static int32
bdw_interrupt_handler(void* data)
{
intel_info& info = *(intel_info*)data;
bdw_enable_global_interrupts(info, false);
pipes which;
bool shouldHandle = bdw_check_interrupt(info, which);
if (!shouldHandle) {
bdw_enable_global_interrupts(info, true);
return B_UNHANDLED_INTERRUPT;
}
int32 handled = B_HANDLED_INTERRUPT;
while (shouldHandle) {
if (which.HasPipe(INTEL_PIPE_A) && bdw_check_pipe_interrupt(info, INTEL_PIPE_A)) {
handled = release_vblank_sem(info);
bdw_clear_pipe_interrupt(info, INTEL_PIPE_A);
}
if (which.HasPipe(INTEL_PIPE_B) && bdw_check_pipe_interrupt(info, INTEL_PIPE_B)) {
handled = release_vblank_sem(info);
bdw_clear_pipe_interrupt(info, INTEL_PIPE_B);
}
if (which.HasPipe(INTEL_PIPE_C) && bdw_check_pipe_interrupt(info, INTEL_PIPE_C)) {
handled = release_vblank_sem(info);
bdw_clear_pipe_interrupt(info, INTEL_PIPE_C);
}
shouldHandle = bdw_check_interrupt(info, which);
}
bdw_enable_global_interrupts(info, true);
return handled;
}
static int32 static int32
intel_interrupt_handler(void* data) intel_interrupt_handler(void* data)
{ {
intel_info &info = *(intel_info*)data; intel_info &info = *(intel_info*)data;
uint32 reg = find_reg(info, INTEL_INTERRUPT_IDENTITY);
uint32 identity;
identity = read32(info, reg); pipes which;
bool shouldHandle = intel_check_interrupt(info, which);
if (identity == 0) if (!shouldHandle)
return B_UNHANDLED_INTERRUPT; return B_UNHANDLED_INTERRUPT;
int32 handled = B_HANDLED_INTERRUPT; int32 handled = B_HANDLED_INTERRUPT;
while (identity != 0) { while (shouldHandle) {
if (which.HasPipe(INTEL_PIPE_A)) {
uint32 mask = intel_get_interrupt_mask(info, INTEL_PIPE_A, false);
if ((identity & mask) != 0) {
handled = release_vblank_sem(info); handled = release_vblank_sem(info);
// make sure we'll get another one of those intel_clear_pipe_interrupt(info, INTEL_PIPE_A);
write32(info, INTEL_DISPLAY_A_PIPE_STATUS,
DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
} }
mask = intel_get_interrupt_mask(info, INTEL_PIPE_B, false); if (which.HasPipe(INTEL_PIPE_B)) {
if ((identity & mask) != 0) {
handled = release_vblank_sem(info); handled = release_vblank_sem(info);
// make sure we'll get another one of those intel_clear_pipe_interrupt(info, INTEL_PIPE_B);
write32(info, INTEL_DISPLAY_B_PIPE_STATUS,
DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
} }
#if 0 #if 0
// FIXME we don't have support for the 3rd pipe yet // FIXME we don't have support for the 3rd pipe yet
mask = hasPCH ? PCH_INTERRUPT_VBLANK_PIPEC if (which.HasPipe(INTEL_PIPE_C)) {
: 0;
if ((identity & mask) != 0) {
handled = release_vblank_sem(info); handled = release_vblank_sem(info);
// make sure we'll get another one of those intel_clear_pipe_interrupt(info, INTEL_PIPE_C);
write32(info, INTEL_DISPLAY_C_PIPE_STATUS,
DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
} }
#endif #endif
// setting the bit clears it! shouldHandle = intel_check_interrupt(info, which);
write32(info, reg, identity);
// update our identity register with the remaining interupts
identity = read32(info, reg);
} }
return handled; return handled;
@@ -242,23 +424,26 @@ init_interrupt_handler(intel_info &info)
info.fake_interrupts = false; info.fake_interrupts = false;
status = install_io_interrupt_handler(info.irq, if (info.device_type.Generation() >= 8) {
&intel_interrupt_handler, (void*)&info, 0); status = install_io_interrupt_handler(info.irq,
if (status == B_OK) { &bdw_interrupt_handler, (void*)&info, 0);
write32(info, INTEL_DISPLAY_A_PIPE_STATUS, if (status == B_OK) {
DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED); bdw_enable_global_interrupts(info, true);
write32(info, INTEL_DISPLAY_B_PIPE_STATUS, bdw_enable_interrupts(info, INTEL_PIPE_A, true);
DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED); bdw_enable_interrupts(info, INTEL_PIPE_B, true);
}
} else {
status = install_io_interrupt_handler(info.irq,
&intel_interrupt_handler, (void*)&info, 0);
if (status == B_OK) {
g35_clear_interrupt_status(info, INTEL_PIPE_A);
g35_clear_interrupt_status(info, INTEL_PIPE_B);
uint32 enable = intel_get_interrupt_mask(info, pipes which;
INTEL_PIPE_A | INTEL_PIPE_B, true); which.SetPipe(INTEL_PIPE_A);
which.SetPipe(INTEL_PIPE_B);
// Clear all the interrupts intel_enable_interrupts(info, which, true);
write32(info, find_reg(info, INTEL_INTERRUPT_IDENTITY), ~0); }
// enable interrupts - we only want VBLANK interrupts
write32(info, find_reg(info, INTEL_INTERRUPT_ENABLED), enable);
write32(info, find_reg(info, INTEL_INTERRUPT_MASK), ~enable);
} }
} }
if (status < B_OK) { if (status < B_OK) {