/* * Copyright (c) 2006-2022, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2022-11-07 GuEe-GUI first version */ #include #include #include #define DBG_TAG "pic.gicv2m" #define DBG_LVL DBG_INFO #include #include #include #include "pic-gic-common.h" /* * MSI_TYPER: * [31:26] Reserved * [25:16] lowest SPI assigned to MSI * [15:10] Reserved * [9:0] Numer of SPIs assigned to MSI */ #define V2M_MSI_TYPER 0x008 #define V2M_MSI_TYPER_BASE_SHIFT 16 #define V2M_MSI_TYPER_BASE_MASK 0x3ff #define V2M_MSI_TYPER_NUM_MASK 0x3ff #define V2M_MSI_SETSPI_NS 0x040 #define V2M_MIN_SPI 32 #define V2M_MAX_SPI 1019 #define V2M_MSI_IIDR 0xfcc #define V2M_MSI_TYPER_BASE_SPI(x) (((x) >> V2M_MSI_TYPER_BASE_SHIFT) & V2M_MSI_TYPER_BASE_MASK) #define V2M_MSI_TYPER_NUM_SPI(x) ((x) & V2M_MSI_TYPER_NUM_MASK) /* APM X-Gene with GICv2m MSI_IIDR register value */ #define XGENE_GICV2M_MSI_IIDR 0x06000170 /* Broadcom NS2 GICv2m MSI_IIDR register value */ #define BCM_NS2_GICV2M_MSI_IIDR 0x0000013f /* List of flags for specific v2m implementation */ #define GICV2M_NEEDS_SPI_OFFSET 0x00000001 #define GICV2M_GRAVITON_ADDRESS_ONLY 0x00000002 struct gicv2m { struct rt_pic parent; void *base; void *base_phy; rt_uint32_t spi_start; /* The SPI number that MSIs start */ rt_uint32_t spis_nr; /* The number of SPIs for MSIs */ rt_uint32_t spi_offset; /* Offset to be subtracted from SPI number */ rt_bitmap_t *vectors; /* MSI vector bitmap */ rt_uint32_t flags; /* Flags for v2m's specific implementation */ void *gic; struct rt_spinlock lock; }; #define raw_to_gicv2m(raw) rt_container_of(raw, struct gicv2m, parent) static rt_ubase_t gicv2m_get_msi_addr(struct gicv2m *v2m, int hwirq) { rt_ubase_t addr; if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) { addr = (rt_ubase_t)v2m->base_phy | ((hwirq - 32) << 3); } else { addr = (rt_ubase_t)v2m->base_phy + V2M_MSI_SETSPI_NS; } return addr; } static rt_bool_t is_msi_spi_valid(rt_uint32_t base, rt_uint32_t num) { if (base < V2M_MIN_SPI) { LOG_E("Invalid MSI base SPI (base: %u)", base); return RT_FALSE; } else if ((num == 0) || (base + num > V2M_MAX_SPI)) { LOG_E("Number of SPIs (%u) exceed maximum (%u)", num, V2M_MAX_SPI - V2M_MIN_SPI + 1); return RT_FALSE; } return RT_TRUE; } static void gicv2m_irq_mask(struct rt_pic_irq *pirq) { rt_pci_msi_mask_irq(pirq); rt_pic_irq_parent_mask(pirq); } static void gicv2m_irq_unmask(struct rt_pic_irq *pirq) { rt_pci_msi_unmask_irq(pirq); rt_pic_irq_parent_unmask(pirq); } static void gicv2m_compose_msi_msg(struct rt_pic_irq *pirq, struct rt_pci_msi_msg *msg) { rt_ubase_t addr; struct gicv2m *v2m = raw_to_gicv2m(pirq->pic); addr = gicv2m_get_msi_addr(v2m, pirq->hwirq); msg->address_hi = rt_upper_32_bits(addr); msg->address_lo = rt_lower_32_bits(addr); if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) { msg->data = 0; } else { msg->data = pirq->hwirq; } if (v2m->flags & GICV2M_NEEDS_SPI_OFFSET) { msg->data -= v2m->spi_offset; } } static int gicv2m_irq_alloc_msi(struct rt_pic *pic, struct rt_pci_msi_desc *msi_desc) { rt_ubase_t level; int irq, parent_irq, hwirq, hwirq_index; struct rt_pic_irq *pirq; struct gicv2m *v2m = raw_to_gicv2m(pic); struct rt_pic *ppic = v2m->gic; level = rt_spin_lock_irqsave(&v2m->lock); hwirq_index = rt_bitmap_next_clear_bit(v2m->vectors, 0, v2m->spis_nr); if (hwirq_index >= v2m->spis_nr) { irq = -RT_EEMPTY; goto _out_lock; } hwirq = v2m->spi_start + v2m->spi_offset + hwirq_index; parent_irq = ppic->ops->irq_map(ppic, hwirq, RT_IRQ_MODE_EDGE_RISING); if (parent_irq < 0) { irq = parent_irq; goto _out_lock; } irq = rt_pic_config_irq(pic, hwirq_index, hwirq); if (irq < 0) { goto _out_lock; } pirq = rt_pic_find_irq(pic, hwirq_index); pirq->mode = RT_IRQ_MODE_EDGE_RISING; rt_pic_cascade(pirq, parent_irq); rt_bitmap_set_bit(v2m->vectors, hwirq_index); _out_lock: rt_spin_unlock_irqrestore(&v2m->lock, level); return irq; } static void gicv2m_irq_free_msi(struct rt_pic *pic, int irq) { rt_ubase_t level; struct rt_pic_irq *pirq; struct gicv2m *v2m = raw_to_gicv2m(pic); pirq = rt_pic_find_pirq(pic, irq); if (!pirq) { return; } rt_pic_uncascade(pirq); level = rt_spin_lock_irqsave(&v2m->lock); rt_bitmap_clear_bit(v2m->vectors, pirq->hwirq - (v2m->spi_start + v2m->spi_offset)); rt_spin_unlock_irqrestore(&v2m->lock, level); } static rt_err_t gicv2m_irq_set_state(struct rt_pic *pic, int hwirq, int type, rt_bool_t state) { struct gicv2m *v2m = raw_to_gicv2m(pic); struct rt_pic *ppic = v2m->gic; return ppic->ops->irq_set_state(ppic, hwirq, type, state); } static rt_err_t gicv2m_irq_get_state(struct rt_pic *pic, int hwirq, int type, rt_bool_t *out_state) { struct gicv2m *v2m = raw_to_gicv2m(pic); struct rt_pic *ppic = v2m->gic; return ppic->ops->irq_get_state(ppic, hwirq, type, out_state); } const static struct rt_pic_ops gicv2m_ops = { .name = "GICv2m", .irq_ack = rt_pic_irq_parent_ack, .irq_mask = gicv2m_irq_mask, .irq_unmask = gicv2m_irq_unmask, .irq_eoi = rt_pic_irq_parent_eoi, .irq_set_priority = rt_pic_irq_parent_set_priority, .irq_set_affinity = rt_pic_irq_parent_set_affinity, .irq_compose_msi_msg = gicv2m_compose_msi_msg, .irq_alloc_msi = gicv2m_irq_alloc_msi, .irq_free_msi = gicv2m_irq_free_msi, .irq_set_state = gicv2m_irq_set_state, .irq_get_state = gicv2m_irq_get_state, .flags = RT_PIC_F_IRQ_ROUTING, }; static const struct rt_ofw_node_id gicv2m_ofw_match[] = { { .compatible = "arm,gic-v2m-frame" }, { /* sentinel */ } }; rt_err_t gicv2m_ofw_probe(struct rt_ofw_node *np, const struct rt_ofw_node_id *id) { rt_err_t err = RT_EOK; struct rt_ofw_node *v2m_np; rt_ofw_foreach_available_child_node(np, v2m_np) { struct gicv2m *v2m; rt_size_t bitmap_size; rt_uint32_t spi_start = 0, spis_nr = 0; if (!rt_ofw_node_match(v2m_np, gicv2m_ofw_match)) { continue; } if (!rt_ofw_prop_read_bool(v2m_np, "msi-controller")) { continue; } if (!(v2m = rt_malloc(sizeof(*v2m)))) { rt_ofw_node_put(v2m_np); err = -RT_ENOMEM; break; } v2m->base = rt_ofw_iomap(v2m_np, 0); if (!v2m->base) { LOG_E("%s: IO map failed", rt_ofw_node_full_name(v2m_np)); continue; } v2m->base_phy = rt_kmem_v2p(v2m->base); v2m->flags = 0; if (!rt_ofw_prop_read_u32(v2m_np, "arm,msi-base-spi", &spi_start) && !rt_ofw_prop_read_u32(v2m_np, "arm,msi-num-spis", &spis_nr)) { LOG_I("DT overriding V2M MSI_TYPER (base:%u, num:%u)", spi_start, spis_nr); } if (spi_start && spis_nr) { v2m->spi_start = spi_start; v2m->spis_nr = spis_nr; } else { rt_uint32_t typer; /* Graviton should always have explicit spi_start/spis_nr */ if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) { goto _fail; } typer = HWREG32(v2m->base + V2M_MSI_TYPER); v2m->spi_start = V2M_MSI_TYPER_BASE_SPI(typer); v2m->spis_nr = V2M_MSI_TYPER_NUM_SPI(typer); } if (!is_msi_spi_valid(v2m->spi_start, v2m->spis_nr)) { goto _fail; } /* * APM X-Gene GICv2m implementation has an erratum where * the MSI data needs to be the offset from the spi_start * in order to trigger the correct MSI interrupt. This is * different from the standard GICv2m implementation where * the MSI data is the absolute value within the range from * spi_start to (spi_start + num_spis). * * Broadcom NS2 GICv2m implementation has an erratum where the MSI data * is 'spi_number - 32' * * Reading that register fails on the Graviton implementation */ if (!(v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY)) { switch (HWREG32(v2m->base + V2M_MSI_IIDR)) { case XGENE_GICV2M_MSI_IIDR: v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; v2m->spi_offset = v2m->spi_start; break; case BCM_NS2_GICV2M_MSI_IIDR: v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; v2m->spi_offset = 32; break; } } bitmap_size = RT_BITMAP_LEN(v2m->spis_nr) * sizeof(bitmap_t); if (!(v2m->vectors = rt_calloc(1, bitmap_size))) { err = -RT_ENOMEM; goto _fail; } rt_spin_lock_init(&v2m->lock); v2m->parent.priv_data = v2m; v2m->parent.ops = &gicv2m_ops; v2m->gic = rt_ofw_data(np); rt_pic_linear_irq(&v2m->parent, v2m->spis_nr); rt_pic_user_extends(&v2m->parent); rt_ofw_data(v2m_np) = &v2m->parent; rt_ofw_node_set_flag(v2m_np, RT_OFW_F_READLY); continue; _fail: rt_iounmap(v2m->base); rt_free(v2m); if (err) { rt_ofw_node_put(v2m_np); break; } } return err; }