fix: kernel concurrency, interrupt context, and user VA safety
This commit is contained in:
+78
-14
@@ -10,6 +10,7 @@
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Io/IoPort.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
@@ -19,6 +20,14 @@ namespace Pci {
|
||||
static constexpr uint16_t ConfigAddressPort = 0xCF8;
|
||||
static constexpr uint16_t ConfigDataPort = 0xCFC;
|
||||
|
||||
// CF8/CFC is one machine-wide address/data latch, not a per-CPU interface.
|
||||
// Keep the address write and matching data access indivisible: otherwise a
|
||||
// second CPU can replace CF8 between them and make us read or write an
|
||||
// unrelated device's register. Besides bogus probing, a crossed runtime
|
||||
// command/MSI write can disable interrupts or bus mastering underneath an
|
||||
// active GPU, USB controller, NIC, or HDA controller.
|
||||
static kcp::Spinlock g_legacyConfigLock;
|
||||
|
||||
// PCI config space register offsets
|
||||
static constexpr uint16_t RegVendorId = 0x00;
|
||||
static constexpr uint16_t RegDeviceId = 0x02;
|
||||
@@ -163,11 +172,26 @@ namespace Pci {
|
||||
| (offset & 0xFC);
|
||||
}
|
||||
|
||||
uint32_t LegacyRead32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset) {
|
||||
static uint32_t LegacyRead32Unlocked(uint8_t bus, uint8_t device,
|
||||
uint8_t function, uint8_t offset) {
|
||||
Io::Out32(LegacyBuildAddress(bus, device, function, offset), ConfigAddressPort);
|
||||
return Io::In32(ConfigDataPort);
|
||||
}
|
||||
|
||||
static void LegacyWrite32Unlocked(uint8_t bus, uint8_t device,
|
||||
uint8_t function, uint8_t offset,
|
||||
uint32_t value) {
|
||||
Io::Out32(LegacyBuildAddress(bus, device, function, offset), ConfigAddressPort);
|
||||
Io::Out32(value, ConfigDataPort);
|
||||
}
|
||||
|
||||
uint32_t LegacyRead32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset) {
|
||||
g_legacyConfigLock.Acquire();
|
||||
uint32_t value = LegacyRead32Unlocked(bus, device, function, offset);
|
||||
g_legacyConfigLock.Release();
|
||||
return value;
|
||||
}
|
||||
|
||||
uint16_t LegacyRead16(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset) {
|
||||
uint32_t val = LegacyRead32(bus, device, function, offset & 0xFC);
|
||||
return (uint16_t)(val >> ((offset & 2) * 8));
|
||||
@@ -179,30 +203,29 @@ namespace Pci {
|
||||
}
|
||||
|
||||
void LegacyWrite32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value) {
|
||||
Io::Out32(LegacyBuildAddress(bus, device, function, offset), ConfigAddressPort);
|
||||
Io::Out32(value, ConfigDataPort);
|
||||
g_legacyConfigLock.Acquire();
|
||||
LegacyWrite32Unlocked(bus, device, function, offset, value);
|
||||
g_legacyConfigLock.Release();
|
||||
}
|
||||
|
||||
void LegacyWrite16(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint16_t value) {
|
||||
uint32_t addr = LegacyBuildAddress(bus, device, function, offset & 0xFC);
|
||||
Io::Out32(addr, ConfigAddressPort);
|
||||
uint32_t tmp = Io::In32(ConfigDataPort);
|
||||
g_legacyConfigLock.Acquire();
|
||||
uint32_t tmp = LegacyRead32Unlocked(bus, device, function, offset & 0xFC);
|
||||
int shift = (offset & 2) * 8;
|
||||
tmp &= ~(0xFFFF << shift);
|
||||
tmp |= ((uint32_t)value << shift);
|
||||
Io::Out32(addr, ConfigAddressPort);
|
||||
Io::Out32(tmp, ConfigDataPort);
|
||||
LegacyWrite32Unlocked(bus, device, function, offset & 0xFC, tmp);
|
||||
g_legacyConfigLock.Release();
|
||||
}
|
||||
|
||||
void LegacyWrite8(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint8_t value) {
|
||||
uint32_t addr = LegacyBuildAddress(bus, device, function, offset & 0xFC);
|
||||
Io::Out32(addr, ConfigAddressPort);
|
||||
uint32_t tmp = Io::In32(ConfigDataPort);
|
||||
g_legacyConfigLock.Acquire();
|
||||
uint32_t tmp = LegacyRead32Unlocked(bus, device, function, offset & 0xFC);
|
||||
int shift = (offset & 3) * 8;
|
||||
tmp &= ~(0xFF << shift);
|
||||
tmp |= ((uint32_t)value << shift);
|
||||
Io::Out32(addr, ConfigAddressPort);
|
||||
Io::Out32(tmp, ConfigDataPort);
|
||||
LegacyWrite32Unlocked(bus, device, function, offset & 0xFC, tmp);
|
||||
g_legacyConfigLock.Release();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -237,8 +260,17 @@ namespace Pci {
|
||||
// Read Capabilities Pointer (offset 0x34), mask to dword-aligned
|
||||
uint8_t offset = ReadConfig8(bus, device, function, 0x34) & 0xFC;
|
||||
|
||||
// Walk the linked list (cap_id @ +0, next_ptr @ +1)
|
||||
// Walk the conventional 256-byte capability list (cap_id @ +0,
|
||||
// next_ptr @ +1). Firmware owns these pointers, so reject offsets
|
||||
// below the capability area and cycles instead of allowing a corrupt
|
||||
// list to spin forever during device initialization or resume.
|
||||
uint64_t visited = 0;
|
||||
while (offset != 0) {
|
||||
if (offset < 0x40) return 0;
|
||||
uint64_t bit = 1ULL << (offset >> 2);
|
||||
if (visited & bit) return 0;
|
||||
visited |= bit;
|
||||
|
||||
uint8_t id = ReadConfig8(bus, device, function, offset);
|
||||
if (id == capId) {
|
||||
return offset;
|
||||
@@ -249,6 +281,38 @@ namespace Pci {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DisableInterruptDelivery(uint8_t bus, uint8_t device, uint8_t function) {
|
||||
uint16_t command = LegacyRead16(bus, device, function,
|
||||
(uint8_t)PCI_REG_COMMAND);
|
||||
if (!(command & PCI_CMD_INTX_DISABLE)) {
|
||||
LegacyWrite16(bus, device, function, (uint8_t)PCI_REG_COMMAND,
|
||||
command | PCI_CMD_INTX_DISABLE);
|
||||
}
|
||||
|
||||
bool disabledMessageDelivery = false;
|
||||
uint8_t msi = FindCapability(bus, device, function, PCI_CAP_MSI);
|
||||
if (msi != 0) {
|
||||
uint16_t control = LegacyRead16(bus, device, function, msi + 2);
|
||||
if (control & 1) {
|
||||
LegacyWrite16(bus, device, function, msi + 2, control & ~1u);
|
||||
disabledMessageDelivery = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t msix = FindCapability(bus, device, function, PCI_CAP_MSIX);
|
||||
if (msix != 0) {
|
||||
uint16_t control = LegacyRead16(bus, device, function, msix + 2);
|
||||
if (control & (1u << 15)) {
|
||||
// Function-mask before clearing MSI-X Enable so no vector can
|
||||
// escape during the ownership transition.
|
||||
uint16_t disabled = (control | (1u << 14)) & ~(1u << 15);
|
||||
LegacyWrite16(bus, device, function, msix + 2, disabled);
|
||||
disabledMessageDelivery = true;
|
||||
}
|
||||
}
|
||||
return disabledMessageDelivery;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// PCI class code names
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -61,7 +61,8 @@ namespace Pci {
|
||||
void LegacyWrite32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value);
|
||||
|
||||
// PCI capability IDs
|
||||
constexpr uint8_t PCI_CAP_MSI = 0x05;
|
||||
constexpr uint8_t PCI_CAP_MSI = 0x05;
|
||||
constexpr uint8_t PCI_CAP_MSIX = 0x11;
|
||||
|
||||
// Walk the PCI capability linked list for a given device.
|
||||
// Returns the config-space offset of the capability, or 0 if not found.
|
||||
@@ -96,6 +97,10 @@ namespace Pci {
|
||||
// Enable memory space access and bus mastering in PCI command register.
|
||||
void EnableBusMaster(uint8_t bus, uint8_t device, uint8_t function);
|
||||
|
||||
// Disable legacy INTx plus any enabled MSI/MSI-X capability. Returns true
|
||||
// when message-signalled delivery had been enabled and was turned off.
|
||||
bool DisableInterruptDelivery(uint8_t bus, uint8_t device, uint8_t function);
|
||||
|
||||
// =========================================================================
|
||||
// PCI driver matching
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user