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 
19 p2ra_state_t g_32bit_targets;
20 p2ra_state_t g_64bit_targets;
21 
22 static bool g_msi_initialized = false;
arm_gicv2m_msi_init()23 zx_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 
63 finished:
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 
arm_gicv2m_msi_alloc_block(uint requested_irqs,bool can_target_64bit,bool is_msix,msi_block_t * out_block)73 zx_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 
arm_gicv2m_msi_is_supported()141 bool arm_gicv2m_msi_is_supported() {
142     return g_msi_initialized;
143 }
144 
arm_gicv2m_msi_supports_masking()145 bool arm_gicv2m_msi_supports_masking() {
146     return g_msi_initialized;
147 }
148 
arm_gicv2m_msi_free_block(msi_block_t * block)149 void 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 
arm_gicv2m_msi_register_handler(const msi_block_t * block,uint msi_id,int_handler handler,void * ctx)159 void 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 
arm_gicv2m_msi_mask_unmask(const msi_block_t * block,uint msi_id,bool mask)169 void 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