| /* |
| * Copyright (c) 2019 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/drivers/pcie/msi.h> |
| #include <zephyr/drivers/pcie/cap.h> |
| |
| /* functions documented in include/drivers/pcie/msi.h */ |
| |
| static uint32_t pcie_msi_base(pcie_bdf_t bdf, bool *msi) |
| { |
| uint32_t base; |
| |
| if (msi != NULL) { |
| *msi = true; |
| } |
| |
| base = pcie_get_cap(bdf, PCI_CAP_ID_MSI); |
| |
| if (IS_ENABLED(CONFIG_PCIE_MSI_X)) { |
| uint32_t base_msix; |
| |
| base_msix = pcie_get_cap(bdf, PCI_CAP_ID_MSIX); |
| if (base_msix != 0U) { |
| base = base_msix; |
| |
| if (msi != NULL) { |
| *msi = false; |
| } |
| } |
| } |
| |
| return base; |
| } |
| |
| #ifdef CONFIG_PCIE_MSI_MULTI_VECTOR |
| |
| #include <zephyr/kernel/mm.h> |
| |
| __weak uint8_t arch_pcie_msi_vectors_allocate(unsigned int priority, |
| msi_vector_t *vectors, |
| uint8_t n_vector) |
| { |
| ARG_UNUSED(priority); |
| ARG_UNUSED(vectors); |
| ARG_UNUSED(n_vector); |
| |
| return 0; |
| } |
| |
| |
| __weak bool arch_pcie_msi_vector_connect(msi_vector_t *vector, |
| void (*routine)(const void *parameter), |
| const void *parameter, |
| uint32_t flags) |
| { |
| ARG_UNUSED(vector); |
| ARG_UNUSED(routine); |
| ARG_UNUSED(parameter); |
| ARG_UNUSED(flags); |
| |
| return false; |
| } |
| |
| #ifdef CONFIG_PCIE_MSI_X |
| |
| static uint32_t get_msix_table_size(pcie_bdf_t bdf, |
| uint32_t base) |
| { |
| uint32_t mcr; |
| |
| mcr = pcie_conf_read(bdf, base + PCIE_MSIX_MCR); |
| |
| return ((mcr & PCIE_MSIX_MCR_TSIZE) >> PCIE_MSIX_MCR_TSIZE_SHIFT) + 1; |
| } |
| |
| static bool map_msix_table_entries(pcie_bdf_t bdf, |
| uint32_t base, |
| msi_vector_t *vectors, |
| uint8_t n_vector) |
| { |
| uint32_t table_offset; |
| uint8_t table_bir; |
| struct pcie_bar bar; |
| uintptr_t mapped_table; |
| int i; |
| |
| table_offset = pcie_conf_read(bdf, base + PCIE_MSIX_TR); |
| table_bir = table_offset & PCIE_MSIX_TR_BIR; |
| table_offset &= PCIE_MSIX_TR_OFFSET; |
| |
| if (!pcie_get_mbar(bdf, table_bir, &bar)) { |
| return false; |
| } |
| |
| z_phys_map((uint8_t **)&mapped_table, |
| bar.phys_addr + table_offset, |
| n_vector * PCIE_MSIR_TABLE_ENTRY_SIZE, K_MEM_PERM_RW); |
| |
| for (i = 0; i < n_vector; i++) { |
| vectors[i].msix_vector = (struct msix_vector *) |
| (mapped_table + (i * PCIE_MSIR_TABLE_ENTRY_SIZE)); |
| } |
| |
| return true; |
| } |
| |
| static void set_msix(msi_vector_t *vectors, |
| uint8_t n_vector, |
| bool msix) |
| { |
| int i; |
| |
| for (i = 0; i < n_vector; i++) { |
| vectors[i].msix = msix; |
| } |
| } |
| |
| #else |
| #define get_msix_table_size(...) 0 |
| #define map_msix_table_entries(...) true |
| #define set_msix(...) |
| #endif /* CONFIG_PCIE_MSI_X */ |
| |
| static uint32_t get_msi_mmc(pcie_bdf_t bdf, |
| uint32_t base) |
| { |
| uint32_t mcr; |
| |
| mcr = pcie_conf_read(bdf, base + PCIE_MSI_MCR); |
| |
| /* Getting MMC true count: 2^(MMC field) */ |
| return 1 << ((mcr & PCIE_MSI_MCR_MMC) >> PCIE_MSI_MCR_MMC_SHIFT); |
| } |
| |
| uint8_t pcie_msi_vectors_allocate(pcie_bdf_t bdf, |
| unsigned int priority, |
| msi_vector_t *vectors, |
| uint8_t n_vector) |
| { |
| uint32_t req_vectors; |
| uint32_t base; |
| bool msi; |
| |
| base = pcie_msi_base(bdf, &msi); |
| |
| if (IS_ENABLED(CONFIG_PCIE_MSI_X)) { |
| set_msix(vectors, n_vector, !msi); |
| |
| if (!msi) { |
| req_vectors = get_msix_table_size(bdf, base); |
| if (!map_msix_table_entries(bdf, base, |
| vectors, n_vector)) { |
| return 0; |
| } |
| } |
| } |
| |
| if (msi) { |
| req_vectors = get_msi_mmc(bdf, base); |
| } |
| |
| if (n_vector > req_vectors) { |
| n_vector = req_vectors; |
| } |
| |
| for (req_vectors = 0; req_vectors < n_vector; req_vectors++) { |
| vectors[req_vectors].bdf = bdf; |
| } |
| |
| return arch_pcie_msi_vectors_allocate(priority, vectors, n_vector); |
| } |
| |
| bool pcie_msi_vector_connect(pcie_bdf_t bdf, |
| msi_vector_t *vector, |
| void (*routine)(const void *parameter), |
| const void *parameter, |
| uint32_t flags) |
| { |
| uint32_t base; |
| |
| base = pcie_msi_base(bdf, NULL); |
| if (base == 0U) { |
| return false; |
| } |
| |
| return arch_pcie_msi_vector_connect(vector, routine, parameter, flags); |
| } |
| |
| #endif /* CONFIG_PCIE_MSI_MULTI_VECTOR */ |
| |
| #ifdef CONFIG_PCIE_MSI_X |
| |
| static void enable_msix(pcie_bdf_t bdf, |
| msi_vector_t *vectors, |
| uint8_t n_vector, |
| uint32_t base, |
| unsigned int irq) |
| { |
| uint32_t mcr; |
| int i; |
| |
| for (i = 0; i < n_vector; i++) { |
| uint32_t map = pcie_msi_map(irq, &vectors[i], 1); |
| uint32_t mdr = pcie_msi_mdr(irq, &vectors[i]); |
| |
| sys_write32(map, (mm_reg_t) &vectors[i].msix_vector->msg_addr); |
| sys_write32(0, (mm_reg_t) &vectors[i].msix_vector->msg_up_addr); |
| sys_write32(mdr, (mm_reg_t) &vectors[i].msix_vector->msg_data); |
| sys_write32(0, (mm_reg_t) &vectors[i].msix_vector->vector_ctrl); |
| } |
| |
| mcr = pcie_conf_read(bdf, base + PCIE_MSIX_MCR); |
| mcr |= PCIE_MSIX_MCR_EN; |
| pcie_conf_write(bdf, base + PCIE_MSIX_MCR, mcr); |
| } |
| |
| #else |
| #define enable_msix(...) |
| #endif /* CONFIG_PCIE_MSI_X */ |
| |
| static void disable_msi(pcie_bdf_t bdf, |
| uint32_t base) |
| { |
| uint32_t mcr; |
| |
| mcr = pcie_conf_read(bdf, base + PCIE_MSI_MCR); |
| mcr &= ~PCIE_MSI_MCR_EN; |
| pcie_conf_write(bdf, base + PCIE_MSI_MCR, mcr); |
| } |
| |
| static void enable_msi(pcie_bdf_t bdf, |
| msi_vector_t *vectors, |
| uint8_t n_vector, |
| uint32_t base, |
| unsigned int irq) |
| { |
| uint32_t mcr; |
| uint32_t map; |
| uint32_t mdr; |
| uint32_t mme; |
| |
| map = pcie_msi_map(irq, vectors, n_vector); |
| pcie_conf_write(bdf, base + PCIE_MSI_MAP0, map); |
| |
| mdr = pcie_msi_mdr(irq, vectors); |
| mcr = pcie_conf_read(bdf, base + PCIE_MSI_MCR); |
| if ((mcr & PCIE_MSI_MCR_64) != 0U) { |
| pcie_conf_write(bdf, base + PCIE_MSI_MAP1_64, 0U); |
| pcie_conf_write(bdf, base + PCIE_MSI_MDR_64, mdr); |
| } else { |
| pcie_conf_write(bdf, base + PCIE_MSI_MDR_32, mdr); |
| } |
| |
| /* Generating MME field (1 counts as a power of 2) */ |
| for (mme = 0; n_vector > 1; mme++) { |
| n_vector >>= 1; |
| } |
| |
| mcr |= mme << PCIE_MSI_MCR_MME_SHIFT; |
| |
| mcr |= PCIE_MSI_MCR_EN; |
| pcie_conf_write(bdf, base + PCIE_MSI_MCR, mcr); |
| } |
| |
| bool pcie_msi_enable(pcie_bdf_t bdf, |
| msi_vector_t *vectors, |
| uint8_t n_vector, |
| unsigned int irq) |
| { |
| uint32_t base; |
| bool msi; |
| |
| base = pcie_msi_base(bdf, &msi); |
| if (base == 0U) { |
| return false; |
| } |
| |
| if (!msi && IS_ENABLED(CONFIG_PCIE_MSI_X)) { |
| disable_msi(bdf, base); |
| enable_msix(bdf, vectors, n_vector, base, irq); |
| } else { |
| enable_msi(bdf, vectors, n_vector, base, irq); |
| } |
| |
| pcie_set_cmd(bdf, PCIE_CONF_CMDSTAT_MASTER, true); |
| |
| return true; |
| } |
| |
| bool pcie_is_msi(pcie_bdf_t bdf) |
| { |
| return (pcie_msi_base(bdf, NULL) != 0); |
| } |