/* * Copyright (c) 2020 Intel Corporation * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT intel_vt_d #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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);