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