1// Copyright 2016 The Fuchsia Authors 2// Copyright (c) 2016, Google Inc. All rights reserved. 3// 4// Use of this source code is governed by a MIT-style 5// license that can be found in the LICENSE file or at 6// https://opensource.org/licenses/MIT 7 8#include <dev/interrupt/arm_gicv2m.h> 9#include <dev/interrupt/arm_gicv2m_msi.h> 10#include <lib/pow2_range_allocator.h> 11#include <pow2.h> 12#include <string.h> 13#include <sys/types.h> 14#include <trace.h> 15#include <zircon/types.h> 16 17#define LOCAL_TRACE 0 18 19p2ra_state_t g_32bit_targets; 20p2ra_state_t g_64bit_targets; 21 22static bool g_msi_initialized = false; 23zx_status_t arm_gicv2m_msi_init() { 24 zx_status_t ret; 25 26 ret = p2ra_init(&g_32bit_targets, MAX_MSI_IRQS); 27 if (ret != ZX_OK) { 28 TRACEF("Failed to initialize 32 bit allocation pool!\n"); 29 return ret; 30 } 31 32 ret = p2ra_init(&g_64bit_targets, MAX_MSI_IRQS); 33 if (ret != ZX_OK) { 34 TRACEF("Failed to initialize 64 bit allocation pool!\n"); 35 p2ra_free(&g_32bit_targets); 36 return ret; 37 } 38 39 /* TODO(johngro) 40 * 41 * Right now, the pow2 range allocator will not accept overlapping ranges. 42 * It may be possible for fancy GIC implementations to have multiple MSI 43 * frames aligned on 4k boundaries (for virtualisation) with either 44 * completely or partially overlapping IRQ ranges. If/when we need to deal 45 * with hardware like this, we will need to come back here and make this 46 * system more sophisticated. 47 */ 48 arm_gicv2m_frame_info_t info; 49 for (uint i = 0; arm_gicv2m_get_frame_info(i, &info) == ZX_OK; ++i) { 50 p2ra_state_t* pool = ((uint64_t)info.doorbell & 0xFFFFFFFF00000000) 51 ? &g_64bit_targets 52 : &g_32bit_targets; 53 54 uint len = info.end_spi_id - info.start_spi_id + 1; 55 ret = p2ra_add_range(pool, info.start_spi_id, len); 56 if (ret != ZX_OK) { 57 TRACEF("Failed to add MSI IRQ range [%u, %u] to allocator (ret %d).\n", 58 info.start_spi_id, len, ret); 59 goto finished; 60 } 61 } 62 63finished: 64 if (ret != ZX_OK) { 65 p2ra_free(&g_32bit_targets); 66 p2ra_free(&g_64bit_targets); 67 } 68 69 g_msi_initialized = true; 70 return ret; 71} 72 73zx_status_t arm_gicv2m_msi_alloc_block(uint requested_irqs, 74 bool can_target_64bit, 75 bool is_msix, 76 msi_block_t* out_block) { 77 if (!out_block) 78 return ZX_ERR_INVALID_ARGS; 79 80 if (out_block->allocated) 81 return ZX_ERR_BAD_STATE; 82 83 if (!requested_irqs || (requested_irqs > MAX_MSI_IRQS)) 84 return ZX_ERR_INVALID_ARGS; 85 86 zx_status_t ret = ZX_ERR_INTERNAL; 87 bool is_32bit = false; 88 uint alloc_size = 1u << log2_uint_ceil(requested_irqs); 89 uint alloc_start; 90 91 /* If this MSI request can tolerate a 64 bit target address, start by 92 * attempting to allocate from the 64 bit pool */ 93 if (can_target_64bit) 94 ret = p2ra_allocate_range(&g_64bit_targets, alloc_size, &alloc_start); 95 96 /* No allocation yet? Fall back on the 32 bit pool */ 97 if (ret != ZX_OK) { 98 ret = p2ra_allocate_range(&g_32bit_targets, alloc_size, &alloc_start); 99 is_32bit = true; 100 } 101 102 /* If we have not managed to allocate yet, then we fail */ 103 if (ret != ZX_OK) 104 return ret; 105 106 /* Find the target physical address for this allocation. 107 * 108 * TODO(johngro) : we could make this O(k) instead of O(n) by associating a 109 * context pointer with ranges registered with the pow2 allocator. Right 110 * now, however, N tends to be 1, so it is difficult to be too concerned 111 * about this. 112 */ 113 arm_gicv2m_frame_info_t info; 114 for (uint i = 0; (ret = arm_gicv2m_get_frame_info(i, &info)) == ZX_OK; ++i) { 115 uint alloc_end = alloc_start + alloc_size - 1; 116 117 if (((alloc_start >= info.start_spi_id) && (alloc_start <= info.end_spi_id)) && 118 ((alloc_end >= info.start_spi_id) && (alloc_end <= info.end_spi_id))) 119 break; 120 } 121 122 /* This should never ever fail */ 123 DEBUG_ASSERT(ret == ZX_OK); 124 if (ret != ZX_OK) { 125 p2ra_free_range(is_32bit ? &g_32bit_targets : &g_64bit_targets, alloc_start, alloc_size); 126 return ret; 127 } 128 129 LTRACEF("success: base spi %u size %u\n", alloc_start, alloc_size); 130 131 /* Success! Fill out the bookkeeping and we are done */ 132 out_block->platform_ctx = (void*)is_32bit; 133 out_block->base_irq_id = alloc_start; 134 out_block->num_irq = alloc_size; 135 out_block->tgt_addr = info.doorbell; 136 out_block->tgt_data = alloc_start; 137 out_block->allocated = true; 138 return ZX_OK; 139} 140 141bool arm_gicv2m_msi_is_supported() { 142 return g_msi_initialized; 143} 144 145bool arm_gicv2m_msi_supports_masking() { 146 return g_msi_initialized; 147} 148 149void arm_gicv2m_msi_free_block(msi_block_t* block) { 150 DEBUG_ASSERT(block); 151 DEBUG_ASSERT(block->allocated); 152 153 /* We stashed whether or not this came from the 32 bit pool in the platform context pointer */ 154 p2ra_state_t* pool = block->platform_ctx ? &g_32bit_targets : &g_64bit_targets; 155 p2ra_free_range(pool, block->base_irq_id, block->num_irq); 156 memset(block, 0, sizeof(*block)); 157} 158 159void arm_gicv2m_msi_register_handler(const msi_block_t* block, 160 uint msi_id, 161 int_handler handler, 162 void* ctx) { 163 DEBUG_ASSERT(block && block->allocated); 164 DEBUG_ASSERT(msi_id < block->num_irq); 165 zx_status_t status = register_int_handler(block->base_irq_id + msi_id, handler, ctx); 166 DEBUG_ASSERT(status == ZX_OK); 167} 168 169void arm_gicv2m_msi_mask_unmask(const msi_block_t* block, uint msi_id, bool mask) { 170 DEBUG_ASSERT(block && block->allocated); 171 DEBUG_ASSERT(msi_id < block->num_irq); 172 if (mask) 173 mask_interrupt(block->base_irq_id + msi_id); 174 else 175 unmask_interrupt(block->base_irq_id + msi_id); 176} 177