552 lines
13 KiB
C
552 lines
13 KiB
C
/*
|
|
* Copyright (c) 2020 Intel Corporation
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT intel_vt_d
|
|
|
|
#include <errno.h>
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/arch/cpu.h>
|
|
|
|
#include <soc.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/init.h>
|
|
#include <string.h>
|
|
|
|
|
|
#include <zephyr/cache.h>
|
|
|
|
#include <zephyr/arch/x86/intel_vtd.h>
|
|
#include <zephyr/drivers/interrupt_controller/intel_vtd.h>
|
|
#include <zephyr/drivers/interrupt_controller/ioapic.h>
|
|
#include <zephyr/drivers/interrupt_controller/loapic.h>
|
|
#include <zephyr/drivers/pcie/msi.h>
|
|
|
|
#include <kernel_arch_func.h>
|
|
|
|
#include "intc_intel_vtd.h"
|
|
|
|
static inline void vtd_pause_cpu(void)
|
|
{
|
|
__asm__ volatile("pause" ::: "memory");
|
|
}
|
|
|
|
static void vtd_write_reg32(const struct device *dev,
|
|
uint16_t reg, uint32_t value)
|
|
{
|
|
uintptr_t base_address = DEVICE_MMIO_GET(dev);
|
|
|
|
sys_write32(value, (base_address + reg));
|
|
}
|
|
|
|
static uint32_t vtd_read_reg32(const struct device *dev, uint16_t reg)
|
|
{
|
|
uintptr_t base_address = DEVICE_MMIO_GET(dev);
|
|
|
|
return sys_read32(base_address + reg);
|
|
}
|
|
|
|
static void vtd_write_reg64(const struct device *dev,
|
|
uint16_t reg, uint64_t value)
|
|
{
|
|
uintptr_t base_address = DEVICE_MMIO_GET(dev);
|
|
|
|
sys_write64(value, (base_address + reg));
|
|
}
|
|
|
|
static uint64_t vtd_read_reg64(const struct device *dev, uint16_t reg)
|
|
{
|
|
uintptr_t base_address = DEVICE_MMIO_GET(dev);
|
|
|
|
return sys_read64(base_address + reg);
|
|
}
|
|
|
|
static void vtd_send_cmd(const struct device *dev,
|
|
uint16_t cmd_bit, uint16_t status_bit)
|
|
{
|
|
uintptr_t base_address = DEVICE_MMIO_GET(dev);
|
|
uint32_t value;
|
|
|
|
value = vtd_read_reg32(dev, VTD_GSTS_REG);
|
|
value |= BIT(cmd_bit);
|
|
|
|
vtd_write_reg32(dev, VTD_GCMD_REG, value);
|
|
|
|
while (!sys_test_bit((base_address + VTD_GSTS_REG),
|
|
status_bit)) {
|
|
/* Do nothing */
|
|
}
|
|
}
|
|
|
|
static void vtd_flush_irte_from_cache(const struct device *dev,
|
|
uint8_t irte_idx)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
|
|
if (!data->pwc) {
|
|
sys_cache_data_range(&data->irte[irte_idx],
|
|
sizeof(union vtd_irte), K_CACHE_WB);
|
|
}
|
|
}
|
|
|
|
static void vtd_qi_init(const struct device *dev)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
uint64_t value;
|
|
|
|
vtd_write_reg64(dev, VTD_IQT_REG, 0);
|
|
data->qi_tail = 0;
|
|
|
|
value = VTD_IQA_REG_GEN_CONTENT((uintptr_t)data->qi,
|
|
VTD_IQA_WIDTH_128_BIT, QI_SIZE);
|
|
vtd_write_reg64(dev, VTD_IQA_REG, value);
|
|
|
|
vtd_send_cmd(dev, VTD_GCMD_QIE, VTD_GSTS_QIES);
|
|
}
|
|
|
|
static inline void vtd_qi_tail_inc(const struct device *dev)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
|
|
data->qi_tail += sizeof(struct qi_descriptor);
|
|
data->qi_tail %= (QI_NUM * sizeof(struct qi_descriptor));
|
|
}
|
|
|
|
static int vtd_qi_send(const struct device *dev,
|
|
struct qi_descriptor *descriptor)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
union qi_wait_descriptor wait_desc = { 0 };
|
|
struct qi_descriptor *desc;
|
|
uint32_t wait_status;
|
|
uint32_t wait_count;
|
|
|
|
desc = (struct qi_descriptor *)((uintptr_t)data->qi + data->qi_tail);
|
|
|
|
desc->low = descriptor->low;
|
|
desc->high = descriptor->high;
|
|
|
|
vtd_qi_tail_inc(dev);
|
|
|
|
desc++;
|
|
|
|
wait_status = QI_WAIT_STATUS_INCOMPLETE;
|
|
|
|
wait_desc.wait.type = QI_TYPE_WAIT;
|
|
wait_desc.wait.status_write = 1;
|
|
wait_desc.wait.status_data = QI_WAIT_STATUS_COMPLETE;
|
|
wait_desc.wait.address = ((uintptr_t)&wait_status) >> 2;
|
|
|
|
desc->low = wait_desc.desc.low;
|
|
desc->high = wait_desc.desc.high;
|
|
|
|
vtd_qi_tail_inc(dev);
|
|
|
|
vtd_write_reg64(dev, VTD_IQT_REG, data->qi_tail);
|
|
|
|
wait_count = 0;
|
|
|
|
while (wait_status != QI_WAIT_STATUS_COMPLETE) {
|
|
/* We cannot use timeout here, this function being called
|
|
* at init time, it might result that the system clock
|
|
* is not initialized yet since VT-D init comes first.
|
|
*/
|
|
if (wait_count > QI_WAIT_COUNT_LIMIT) {
|
|
printk("QI timeout\n");
|
|
return -ETIME;
|
|
}
|
|
|
|
if (vtd_read_reg32(dev, VTD_FSTS_REG) & VTD_FSTS_IQE) {
|
|
printk("QI error\n");
|
|
return -EIO;
|
|
}
|
|
|
|
vtd_pause_cpu();
|
|
wait_count++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vtd_global_cc_invalidate(const struct device *dev)
|
|
{
|
|
union qi_icc_descriptor iec_desc = { 0 };
|
|
|
|
iec_desc.icc.type = QI_TYPE_ICC;
|
|
iec_desc.icc.granularity = 1; /* Global Invalidation requested */
|
|
|
|
return vtd_qi_send(dev, &iec_desc.desc);
|
|
}
|
|
|
|
static int vtd_global_iec_invalidate(const struct device *dev)
|
|
{
|
|
union qi_iec_descriptor iec_desc = { 0 };
|
|
|
|
iec_desc.iec.type = QI_TYPE_IEC;
|
|
iec_desc.iec.granularity = 0; /* Global Invalidation requested */
|
|
|
|
return vtd_qi_send(dev, &iec_desc.desc);
|
|
}
|
|
|
|
static int vtd_index_iec_invalidate(const struct device *dev, uint8_t irte_idx)
|
|
{
|
|
union qi_iec_descriptor iec_desc = { 0 };
|
|
|
|
iec_desc.iec.type = QI_TYPE_IEC;
|
|
iec_desc.iec.granularity = 1; /* Index based invalidation requested */
|
|
|
|
iec_desc.iec.interrupt_index = irte_idx;
|
|
iec_desc.iec.index_mask = 0;
|
|
|
|
return vtd_qi_send(dev, &iec_desc.desc);
|
|
}
|
|
|
|
static void fault_status_description(uint32_t status)
|
|
{
|
|
if (status & VTD_FSTS_PFO) {
|
|
printk("Primary Fault Overflow (PFO)\n");
|
|
}
|
|
|
|
if (status & VTD_FSTS_AFO) {
|
|
printk("Advanced Fault Overflow (AFO)\n");
|
|
}
|
|
|
|
if (status & VTD_FSTS_APF) {
|
|
printk("Advanced Primary Fault (APF)\n");
|
|
}
|
|
|
|
if (status & VTD_FSTS_IQE) {
|
|
printk("Invalidation Queue Error (IQE)\n");
|
|
}
|
|
|
|
if (status & VTD_FSTS_ICE) {
|
|
printk("Invalidation Completion Error (ICE)\n");
|
|
}
|
|
|
|
if (status & VTD_FSTS_ITE) {
|
|
printk("Invalidation Timeout Error\n");
|
|
}
|
|
|
|
if (status & VTD_FSTS_PPF) {
|
|
printk("Primary Pending Fault (PPF) %u\n",
|
|
VTD_FSTS_FRI(status));
|
|
}
|
|
}
|
|
|
|
static void fault_record_description(uint64_t low, uint64_t high)
|
|
{
|
|
printk("Fault %s request: Reason 0x%x info 0x%llx src 0x%x\n",
|
|
(high & VTD_FRCD_T) ? "Read/Atomic" : "Write/Page",
|
|
VTD_FRCD_FR(high), VTD_FRCD_FI(low), VTD_FRCD_SID(high));
|
|
}
|
|
|
|
static void fault_event_isr(const void *arg)
|
|
{
|
|
const struct device *dev = arg;
|
|
struct vtd_ictl_data *data = dev->data;
|
|
uint32_t status;
|
|
uint8_t f_idx;
|
|
|
|
status = vtd_read_reg32(dev, VTD_FSTS_REG);
|
|
fault_status_description(status);
|
|
|
|
if (!(status & VTD_FSTS_PPF)) {
|
|
goto out;
|
|
}
|
|
|
|
f_idx = VTD_FSTS_FRI(status);
|
|
while (f_idx < data->fault_record_num) {
|
|
uint64_t fault_l, fault_h;
|
|
|
|
/* Reading fault's 64 lowest bits */
|
|
fault_l = vtd_read_reg64(dev, data->fault_record_reg +
|
|
(VTD_FRCD_REG_SIZE * f_idx));
|
|
/* Reading fault's 64 highest bits */
|
|
fault_h = vtd_read_reg64(dev, data->fault_record_reg +
|
|
(VTD_FRCD_REG_SIZE * f_idx) + 8);
|
|
|
|
if (fault_h & VTD_FRCD_F) {
|
|
fault_record_description(fault_l, fault_h);
|
|
}
|
|
|
|
/* Clearing the fault */
|
|
vtd_write_reg64(dev, data->fault_record_reg +
|
|
(VTD_FRCD_REG_SIZE * f_idx), fault_l);
|
|
vtd_write_reg64(dev, data->fault_record_reg +
|
|
(VTD_FRCD_REG_SIZE * f_idx) + 8, fault_h);
|
|
f_idx++;
|
|
}
|
|
out:
|
|
/* Clearing fault status */
|
|
vtd_write_reg32(dev, VTD_FSTS_REG, VTD_FSTS_CLEAR(status));
|
|
}
|
|
|
|
static void vtd_fault_event_init(const struct device *dev)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
uint64_t value;
|
|
uint32_t reg;
|
|
|
|
value = vtd_read_reg64(dev, VTD_CAP_REG);
|
|
data->fault_record_num = VTD_CAP_NFR(value) + 1;
|
|
data->fault_record_reg = DEVICE_MMIO_GET(dev) +
|
|
(uintptr_t)(16 * VTD_CAP_FRO(value));
|
|
|
|
/* Allocating IRQ & vector and connecting the ISR handler,
|
|
* by-passing remapping by using x86 functions directly.
|
|
*/
|
|
data->fault_irq = arch_irq_allocate();
|
|
data->fault_vector = z_x86_allocate_vector(0, -1);
|
|
|
|
vtd_write_reg32(dev, VTD_FEDATA_REG, data->fault_vector);
|
|
vtd_write_reg32(dev, VTD_FEADDR_REG,
|
|
pcie_msi_map(data->fault_irq, NULL, 0));
|
|
vtd_write_reg32(dev, VTD_FEUADDR_REG, 0);
|
|
|
|
z_x86_irq_connect_on_vector(data->fault_irq, data->fault_vector,
|
|
fault_event_isr, dev);
|
|
|
|
vtd_write_reg32(dev, VTD_FSTS_REG,
|
|
VTD_FSTS_CLEAR(vtd_read_reg32(dev, VTD_FSTS_REG)));
|
|
|
|
/* Unmasking interrupts */
|
|
reg = vtd_read_reg32(dev, VTD_FECTL_REG);
|
|
reg &= ~BIT(VTD_FECTL_REG_IM);
|
|
vtd_write_reg32(dev, VTD_FECTL_REG, reg);
|
|
}
|
|
|
|
static int vtd_ictl_allocate_entries(const struct device *dev,
|
|
uint8_t n_entries)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
int irte_idx_start;
|
|
|
|
if ((data->irte_num_used + n_entries) > IRTE_NUM) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
irte_idx_start = data->irte_num_used;
|
|
data->irte_num_used += n_entries;
|
|
|
|
return irte_idx_start;
|
|
}
|
|
|
|
static uint32_t vtd_ictl_remap_msi(const struct device *dev,
|
|
msi_vector_t *vector,
|
|
uint8_t n_vector)
|
|
{
|
|
uint32_t shv = (n_vector > 1) ? VTD_INT_SHV : 0;
|
|
|
|
return VTD_MSI_MAP(vector->arch.irte, shv);
|
|
}
|
|
|
|
static int vtd_ictl_remap(const struct device *dev,
|
|
uint8_t irte_idx,
|
|
uint16_t vector,
|
|
uint32_t flags,
|
|
uint16_t src_id)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
union vtd_irte irte = { 0 };
|
|
uint32_t delivery_mode;
|
|
|
|
irte.bits.vector = vector;
|
|
|
|
if (IS_ENABLED(CONFIG_X2APIC)) {
|
|
/* Getting the logical APIC ID */
|
|
irte.bits.dst_id = x86_read_loapic(LOAPIC_LDR);
|
|
} else {
|
|
/* As for IOAPIC: let's mask all possible IDs */
|
|
irte.bits.dst_id = 0xFF << 8;
|
|
}
|
|
|
|
if (src_id != USHRT_MAX &&
|
|
!IS_ENABLED(CONFIG_INTEL_VTD_ICTL_NO_SRC_ID_CHECK)) {
|
|
irte.bits.src_validation_type = 1;
|
|
irte.bits.src_id = src_id;
|
|
}
|
|
|
|
delivery_mode = (flags & IOAPIC_DELIVERY_MODE_MASK);
|
|
if ((delivery_mode != IOAPIC_FIXED) ||
|
|
(delivery_mode != IOAPIC_LOW)) {
|
|
delivery_mode = IOAPIC_LOW;
|
|
}
|
|
|
|
irte.bits.trigger_mode = (flags & IOAPIC_TRIGGER_MASK) >> 15;
|
|
irte.bits.delivery_mode = delivery_mode >> 8;
|
|
irte.bits.redirection_hint = 1;
|
|
irte.bits.dst_mode = 1; /* Always logical */
|
|
irte.bits.present = 1;
|
|
|
|
data->irte[irte_idx].parts.low = irte.parts.low;
|
|
data->irte[irte_idx].parts.high = irte.parts.high;
|
|
|
|
vtd_index_iec_invalidate(dev, irte_idx);
|
|
|
|
vtd_flush_irte_from_cache(dev, irte_idx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vtd_ictl_set_irte_vector(const struct device *dev,
|
|
uint8_t irte_idx,
|
|
uint16_t vector)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
|
|
data->vectors[irte_idx] = vector;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vtd_ictl_get_irte_by_vector(const struct device *dev,
|
|
uint16_t vector)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
int irte_idx;
|
|
|
|
for (irte_idx = 0; irte_idx < IRTE_NUM; irte_idx++) {
|
|
if (data->vectors[irte_idx] == vector) {
|
|
return irte_idx;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static uint16_t vtd_ictl_get_irte_vector(const struct device *dev,
|
|
uint8_t irte_idx)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
|
|
return data->vectors[irte_idx];
|
|
}
|
|
|
|
static int vtd_ictl_set_irte_irq(const struct device *dev,
|
|
uint8_t irte_idx,
|
|
unsigned int irq)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
|
|
data->irqs[irte_idx] = irq;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vtd_ictl_get_irte_by_irq(const struct device *dev,
|
|
unsigned int irq)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
int irte_idx;
|
|
|
|
for (irte_idx = 0; irte_idx < IRTE_NUM; irte_idx++) {
|
|
if (data->irqs[irte_idx] == irq) {
|
|
return irte_idx;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static void vtd_ictl_set_irte_msi(const struct device *dev,
|
|
uint8_t irte_idx, bool msi)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
|
|
data->msi[irte_idx] = msi;
|
|
}
|
|
|
|
static bool vtd_ictl_irte_is_msi(const struct device *dev,
|
|
uint8_t irte_idx)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
|
|
return data->msi[irte_idx];
|
|
}
|
|
|
|
static int vtd_ictl_init(const struct device *dev)
|
|
{
|
|
struct vtd_ictl_data *data = dev->data;
|
|
unsigned int key = irq_lock();
|
|
uint64_t eime = 0;
|
|
uint64_t value;
|
|
int ret = 0;
|
|
|
|
DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
|
|
|
|
if (vtd_read_reg64(dev, VTD_ECAP_REG) & VTD_ECAP_C) {
|
|
printk("Page walk coherency supported\n");
|
|
data->pwc = true;
|
|
}
|
|
|
|
vtd_fault_event_init(dev);
|
|
|
|
vtd_qi_init(dev);
|
|
|
|
if (vtd_global_cc_invalidate(dev) != 0) {
|
|
printk("Could not perform ICC invalidation\n");
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_X2APIC)) {
|
|
eime = VTD_IRTA_EIME;
|
|
}
|
|
|
|
value = VTD_IRTA_REG_GEN_CONTENT((uintptr_t)data->irte,
|
|
IRTA_SIZE, eime);
|
|
|
|
vtd_write_reg64(dev, VTD_IRTA_REG, value);
|
|
|
|
if (vtd_global_iec_invalidate(dev) != 0) {
|
|
printk("Could not perform IEC invalidation\n");
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
if (!IS_ENABLED(CONFIG_X2APIC) &&
|
|
IS_ENABLED(CONFIG_INTEL_VTD_ICTL_XAPIC_PASSTHROUGH)) {
|
|
vtd_send_cmd(dev, VTD_GCMD_CFI, VTD_GSTS_CFIS);
|
|
}
|
|
|
|
vtd_send_cmd(dev, VTD_GCMD_SIRTP, VTD_GSTS_SIRTPS);
|
|
vtd_send_cmd(dev, VTD_GCMD_IRE, VTD_GSTS_IRES);
|
|
|
|
printk("Intel VT-D up and running (status 0x%x)\n",
|
|
vtd_read_reg32(dev, VTD_GSTS_REG));
|
|
|
|
out:
|
|
irq_unlock(key);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct vtd_driver_api vtd_api = {
|
|
.allocate_entries = vtd_ictl_allocate_entries,
|
|
.remap_msi = vtd_ictl_remap_msi,
|
|
.remap = vtd_ictl_remap,
|
|
.set_irte_vector = vtd_ictl_set_irte_vector,
|
|
.get_irte_by_vector = vtd_ictl_get_irte_by_vector,
|
|
.get_irte_vector = vtd_ictl_get_irte_vector,
|
|
.set_irte_irq = vtd_ictl_set_irte_irq,
|
|
.get_irte_by_irq = vtd_ictl_get_irte_by_irq,
|
|
.set_irte_msi = vtd_ictl_set_irte_msi,
|
|
.irte_is_msi = vtd_ictl_irte_is_msi
|
|
};
|
|
|
|
static struct vtd_ictl_data vtd_ictl_data_0 = {
|
|
.irqs = { -EINVAL },
|
|
.vectors = { -EINVAL },
|
|
};
|
|
|
|
static const struct vtd_ictl_cfg vtd_ictl_cfg_0 = {
|
|
DEVICE_MMIO_ROM_INIT(DT_DRV_INST(0)),
|
|
};
|
|
|
|
DEVICE_DT_INST_DEFINE(0,
|
|
vtd_ictl_init, NULL,
|
|
&vtd_ictl_data_0, &vtd_ictl_cfg_0,
|
|
PRE_KERNEL_1, CONFIG_INTEL_VTD_ICTL_INIT_PRIORITY, &vtd_api);
|