| /* |
| * 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) { |
| cache_data_flush_range(&data->irte[irte_idx], |
| sizeof(union vtd_irte)); |
| } |
| } |
| |
| 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); |