Bochs/bochs/cpu/uintr.cc

358 lines
12 KiB
C++

/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2023 Stanislav Shwartsman
// Written by Stanislav Shwartsman [sshwarts at sourceforge net]
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA B 02110-1301 USA
//
/////////////////////////////////////////////////////////////////////////
#define NEED_CPU_REG_SHORTCUTS 1
#include "bochs.h"
#include "cpu.h"
#define LOG_THIS BX_CPU_THIS_PTR
#if BX_SUPPORT_UINTR && BX_SUPPORT_X86_64
#if BX_SUPPORT_APIC
#include "apic.h"
#endif
#include "scalar_arith.h" // for lzcntq
void BX_CPU_C::deliver_UINTR()
{
BX_ASSERT(BX_CPU_THIS_PTR cr4.get_UINTR() && !uintr_masked());
if (!IsCanonical(BX_CPU_THIS_PTR uintr.ui_handler)) {
BX_ERROR(("UINTR.UI_HANDLER is not canonical: #GP(0)"));
exception(BX_GP_EXCEPTION, 0);
}
RSP_SPECULATIVE;
Bit64u tmp_RSP = RSP;
if (BX_CPU_THIS_PTR uintr.stack_adjust & 0x1)
RSP = BX_CPU_THIS_PTR uintr.stack_adjust;
else
RSP -= BX_CPU_THIS_PTR uintr.stack_adjust;
RSP &= BX_CONST64(0xfffffffffffffff0);
push_64(tmp_RSP);
push_64(read_eflags());
push_64(RIP);
BX_ASSERT(BX_CPU_THIS_PTR uintr.uirr != 0);
unsigned vector = most_significant_bitq(BX_CPU_THIS_PTR uintr.uirr); // find #most significant bit in UIRR
push_64(vector);
#if BX_SUPPORT_CET
if (ShadowStackEnabled(3))
shadow_stack_push_64(RIP);
track_indirect(3);
#endif
RSP_COMMIT;
BX_CPU_THIS_PTR uintr.uirr &= ~(BX_CONST64(1) << vector);
uintr_uirr_update(); // potentially clear user-level-interrupt
BX_CPU_THIS_PTR uintr.UIF = 0;
BX_CPU_THIS_PTR clear_TF();
BX_CPU_THIS_PTR clear_RF();
RIP = BX_CPU_THIS_PTR uintr.ui_handler;
uintr_control(); // UIF changed
}
void BX_CPP_AttrRegparmN(1) BX_CPU_C::UIRET(bxInstruction_c *i)
{
if (! BX_CPU_THIS_PTR cr4.get_UINTR()) {
BX_ERROR(("%s: UINTR in not enabled in CR4", i->getIaOpcodeNameShort()));
exception(BX_UD_EXCEPTION, 0);
}
BX_INSTR_FAR_BRANCH_ORIGIN();
Bit64u new_rip = stack_read_qword(RSP + 8);
Bit32u new_eflags = (Bit32u) stack_read_qword(RSP + 16);
Bit64u new_rsp = stack_read_qword(RSP + 24);
if (!IsCanonical(new_rip)) {
BX_ERROR(("UIRET #GP(0): return RIP is not canonical"));
exception(BX_GP_EXCEPTION, 0);
}
#if BX_SUPPORT_CET
if (ShadowStackEnabled(CPL)) {
Bit64u shadow_RIP = shadow_stack_pop_64();
if (new_rip != shadow_RIP) {
BX_ERROR(("shadow_stack_restore: LIP mismatch"));
exception(BX_CP_EXCEPTION, BX_CP_FAR_RET_IRET);
}
}
#endif
RIP = new_rip;
// update in RFLAGS only CF, PF, AF, ZF, SF, TF, DF, OF, NT, RF, AC, and ID
writeEFlags(new_eflags, 0x254DD5);
RSP = new_rsp;
#if BX_SUPPORT_MONITOR_MWAIT
BX_CPU_THIS_PTR monitor.reset_monitor();
#endif
if (BX_CPUID_SUPPORT_ISA_EXTENSION(BX_ISA_FLEXIBLE_UIRET))
BX_CPU_THIS_PTR uintr.UIF = (new_eflags & 0x2) != 0;
else
BX_CPU_THIS_PTR uintr.UIF = 1;
uintr_control(); // potentially enable user interrupt delivery
BX_INSTR_FAR_BRANCH(BX_CPU_ID, BX_INSTR_IS_UIRET,
FAR_BRANCH_PREV_CS, FAR_BRANCH_PREV_RIP,
BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value, RIP);
BX_NEXT_TRACE(i);
}
void BX_CPP_AttrRegparmN(1) BX_CPU_C::STUI(bxInstruction_c *i)
{
if (! BX_CPU_THIS_PTR cr4.get_UINTR()) {
BX_ERROR(("%s: UINTR in not enabled in CR4", i->getIaOpcodeNameShort()));
exception(BX_UD_EXCEPTION, 0);
}
BX_CPU_THIS_PTR uintr.UIF = 1;
uintr_control(); // potentially enable user interrupt delivery
BX_NEXT_TRACE(i);
}
void BX_CPP_AttrRegparmN(1) BX_CPU_C::CLUI(bxInstruction_c *i)
{
if (! BX_CPU_THIS_PTR cr4.get_UINTR()) {
BX_ERROR(("%s: UINTR in not enabled in CR4", i->getIaOpcodeNameShort()));
exception(BX_UD_EXCEPTION, 0);
}
BX_CPU_THIS_PTR uintr.UIF = 0;
uintr_control(); // disable user interrupt delivery
BX_NEXT_INSTR(i);
}
void BX_CPP_AttrRegparmN(1) BX_CPU_C::TESTUI(bxInstruction_c *i)
{
if (! BX_CPU_THIS_PTR cr4.get_UINTR()) {
BX_ERROR(("%s: UINTR in not enabled in CR4", i->getIaOpcodeNameShort()));
exception(BX_UD_EXCEPTION, 0);
}
clearEFlagsOSZAPC();
BX_CPU_THIS_PTR set_CF(BX_CPU_THIS_PTR uintr.UIF);
BX_NEXT_INSTR(i);
}
void BX_CPP_AttrRegparmN(1) BX_CPU_C::SENDUIPI_Gq(bxInstruction_c *i)
{
if (! BX_CPU_THIS_PTR cr4.get_UINTR()) {
BX_ERROR(("%s: UINTR in not enabled in CR4", i->getIaOpcodeNameShort()));
exception(BX_UD_EXCEPTION, 0);
}
if (! BX_CPU_THIS_PTR uintr.senduipi_enabled()) {
BX_ERROR(("SENDUIPI in disabled by IA32_UINTR_TT[0]"));
exception(BX_UD_EXCEPTION, 0);
}
Bit64u index = BX_READ_64BIT_REG(i->src());
if (index > BX_CPU_THIS_PTR uintr.uitt_size) {
BX_ERROR(("SENDUIPI: value of the source operand exceeds UITT.SIZE"));
exception(BX_GP_EXCEPTION, 0);
}
// ----------- UITT format --------
// | 00 | Valid
// | 07:01 | Reserved, MBZ
// | 15:08 | UVector, user-level interrupt vector, must be < 64
// | 63:16 | Reserved, MBZ
// | 127:64 | UPID_ADDR, linear address of UPID, must be 64-byte aligned
// --------------------------------
bx_address entry_addr = (BX_CPU_THIS_PTR uintr.uitt_addr & ~BX_CONST64(0xF)) + index * 16;
Bit64u tmpUITT_entry_lo = system_read_qword(entry_addr);
Bit64u tmpUITT_upidaddr = system_read_qword(entry_addr + 8);
if ((tmpUITT_entry_lo & 0x1) == 0) {
BX_ERROR(("SENDUIPI #GP(0): invalid UITT entry"));
exception(BX_GP_EXCEPTION, 0);
}
if (tmpUITT_entry_lo & BX_CONST64(0xFFFFFFFFFFFF00FE)) {
BX_ERROR(("SENDUIPI #GP(0): invalid UITT entry, reserved bits set"));
exception(BX_GP_EXCEPTION, 0);
}
unsigned uvector = (tmpUITT_entry_lo >> 8) & 0xFF;
if (uvector >= 64) {
BX_ERROR(("SENDUIPI #GP(0): UVector=%d >= 64", uvector));
exception(BX_GP_EXCEPTION, 0);
}
if (tmpUITT_upidaddr & 0x3F) {
BX_ERROR(("SENDUIPI #GP(0): UPID_ADDR must be 64-byte aligned"));
exception(BX_GP_EXCEPTION, 0);
}
// ----------- UPID format --------
// | 00 | Outstanding notification (UPID.ON)
// | 01 | Suppress notification (UPID.SN)
// | 15:02 | Reserved, MBZ
// | 23:16 | Notification vector, 8-bit
// | 31:24 | Reserved, MBZ
// | 63:32 | Notification destination, 32-bit APIC/X2APIC ID
// | 127:64 | Posted interrupt requests (PIR)
// | | One bit for each user-interrupt vector
// --------------------------------
bool send_notify = false;
Bit32u notification_vector = 0;
Bit32u notification_destination = 0;
// should be done atomically using RMW
Bit64u tmpUPID_lo = system_read_qword(tmpUITT_upidaddr);
Bit64u tmpUPID_hi = system_read_qword(tmpUITT_upidaddr + 8);
if (tmpUPID_lo & BX_CONST64(0xFF00FFFC)) {
BX_ERROR(("SENDUIPI #GP(0): invalid UPID, reserved bits set"));
exception(BX_GP_EXCEPTION, 0);
}
tmpUPID_hi |= (BX_CONST64(1) << uvector); // PIR[uvector] = 1
if ((tmpUPID_lo & 0x3) == 0) {
tmpUPID_lo |= 1;
send_notify = true;
notification_vector = GET32L(tmpUPID_lo) >> 16;
notification_destination = GET32H(tmpUPID_lo);
}
system_write_qword(tmpUITT_upidaddr, tmpUPID_lo);
system_write_qword(tmpUITT_upidaddr + 8, tmpUPID_hi);
// should be done atomically using RMW
if (send_notify) {
send_uipi(notification_destination, notification_vector);
}
BX_NEXT_TRACE(i);
}
#include "apic.h"
void BX_CPU_C::send_uipi(Bit32u notification_destination, Bit32u notification_vector)
{
#if BX_SUPPORT_VMX
VMCS_CACHE *vm = &BX_CPU_THIS_PTR vmcs;
if (vm->vmexec_ctrls1.TPR_SHADOW()) {
if (vm->vmexec_ctrls2.VIRTUALIZE_APIC_ACCESSES()) {
// virtualize sending of an XAPIC-mode IPI by:
// - writing to VICR_HI[31:24] = 8 bit destination APIC_ID from NDST[15:8]
// VICR_HI[23:00] = 0
VMX_Write_Virtual_APIC(BX_LAPIC_ICR_HI, (notification_destination & 0xff00) << 16);
// - writing to VICR_LO[31:08] = 0 (indicating a physically addressed fixed-mode IPI)
// VICR_HI[07:00] = notification_vector
VMX_Write_Virtual_APIC(BX_LAPIC_ICR_LO, notification_vector);
}
else {
// virtualize sending of an X2APIC-mode IPI by:
// - writing 64-bit value into VICR
// VICR[7:0] = notification_vector
// VICR[31:8] = 0 (indicating a physically addressed fixed-mode IPI)
// VICR[64:32] = 32-bit notification destination
VMX_Write_Virtual_X2APIC(BX_LAPIC_ICR_LO, GET64_FROM_HI32_LO32(notification_destination, notification_vector));
}
VMexit(VMX_VMEXIT_APIC_WRITE, BX_LAPIC_ICR_LO); // trap-like vmexit
return;
}
#endif
// sending of IPI is done by writing to the local APIC's ICR register:
// ICR[7:0] = notification_vector
// ICR[64:32] = the APIC_ID from NDST[15:8] (legacy apic mode) or NDST[31:0] (x2apic mode)
// ICR[31:8] = 0 (indicating a physically addressed fixed-mode IPI)
#if BX_SUPPORT_APIC
BX_CPU_THIS_PTR lapic->send_ipi(x2apic_mode() ? notification_destination : (notification_destination >> 8), notification_vector);
#endif
}
void BX_CPU_C::Process_UINTR_Notification()
{
#if BX_SUPPORT_VMX
// the User-Level Interrupt notification process looks like external interrupt with the vector UINV
// in particular for IDT-vectoring info
if (BX_CPU_THIS_PTR in_vmx_guest) {
VMCS_CACHE *vm = &BX_CPU_THIS_PTR vmcs;
vm->idt_vector_error_code = 0;
vm->idt_vector_info = (BX_CPU_THIS_PTR uintr.uinv) | (BX_EXTERNAL_INTERRUPT << 8);
}
#endif
// Step 1: Clear UPID.ON atomically with locked AND operation
// should be done atomically using RMW
Bit8u tmpUPID_lo8 = system_read_byte(BX_CPU_THIS_PTR uintr.upid_addr);
tmpUPID_lo8 &= 0xFE;
system_write_byte(BX_CPU_THIS_PTR uintr.upid_addr, tmpUPID_lo8);
// should be done atomically using RMW
// Step 2: Read UPID.PIR into temporary PIR register and write '0 to the PIR using locked XCHG operation
// should be done atomically using RMW
Bit64u PIR = system_read_qword(BX_CPU_THIS_PTR uintr.upid_addr + 8);
system_write_qword(BX_CPU_THIS_PTR uintr.upid_addr + 8, 0);
// should be done atomically using RMW
// Step 3: for any bit set in the temporary PIR register set corresponding UIRR bit
// recognize pending user-level interrupt
BX_CPU_THIS_PTR uintr.uirr |= PIR;
uintr_uirr_update(); // potentially signal user-level-interrupt
}
bool BX_CPU_C::uintr_masked()
{
// The user-level interrupt can be delivered only if:
// long64_mode()
// UIF = 1
// CPL = 3
return !long64_mode() || !BX_CPU_THIS_PTR uintr.UIF || CPL != 3;
}
// mask or unmask BX_EVENT_PENDING_UINTR according to conditions telling if it can be potentially delivered
void BX_CPU_C::uintr_uirr_update()
{
// There is a pending user-level interrupt if UINTR.UIRR != 0
if (BX_CPU_THIS_PTR cr4.get_UINTR() && BX_CPU_THIS_PTR uintr.uirr)
signal_event(BX_EVENT_PENDING_UINTR);
else
clear_event(BX_EVENT_PENDING_UINTR);
}
void BX_CPU_C::uintr_control()
{
if (uintr_masked())
mask_event(BX_EVENT_PENDING_UINTR);
else
unmask_event(BX_EVENT_PENDING_UINTR);
}
#endif