1/*
2 * Copyright 2019, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12
13#include <assert.h>
14#include <stdbool.h>
15
16#include <platsupport/fdt.h>
17
18#include "irqchip.h"
19
20/* Force the _allocated_irqs section to be created even if no modules are defined. */
21static USED SECTION("_ps_irqchips") struct {} dummy_ps_irqchips;
22/* Definitions so that we can find the exposed IRQ information */
23extern ps_irqchip_t *__start__ps_irqchips[];
24extern ps_irqchip_t *__stop__ps_irqchips[];
25
26/* Private internal struct */
27struct ps_fdt_cookie {
28    int node_offset;
29};
30
31/* Used for the ps_fdt_index_* helper functions */
32
33typedef struct index_helper_token {
34    ps_io_ops_t *io_ops;
35    unsigned desired_offset;
36    /* These are used for the 'register index' helper */
37    void *mapped_addr;
38    pmem_region_t region;
39    /* These are used for the 'IRQ index' helper */
40    irq_id_t irq_id;
41    irq_callback_fn_t irq_callback;
42    void *irq_callback_data;
43} index_helper_token_t;
44
45int ps_fdt_read_path(ps_io_fdt_t *io_fdt, ps_malloc_ops_t *malloc_ops, const char *path, ps_fdt_cookie_t **ret_cookie)
46{
47    if (!path || !ret_cookie) {
48        return -EINVAL;
49    }
50
51    int error = ps_calloc(malloc_ops, 1, sizeof(**ret_cookie), (void **) ret_cookie);
52    if (error) {
53        return -ENOMEM;
54    }
55
56    char *dtb_blob = ps_io_fdt_get(io_fdt);
57    if (!dtb_blob) {
58        ZF_LOGF_IF(ps_fdt_cleanup_cookie(malloc_ops, *ret_cookie), "Failed to cleanup FDT cookie");
59        return -EINVAL;
60    }
61
62    int node_offset = fdt_path_offset(dtb_blob, path);
63    if (node_offset < 0) {
64        ZF_LOGF_IF(ps_fdt_cleanup_cookie(malloc_ops, *ret_cookie), "Failed to cleanup FDT cookie");
65        return node_offset;
66    }
67
68    (*ret_cookie)->node_offset = node_offset;
69
70    return 0;
71}
72
73int ps_fdt_cleanup_cookie(ps_malloc_ops_t *malloc_ops, ps_fdt_cookie_t *cookie)
74{
75    if (!malloc_ops || !cookie) {
76        return -EINVAL;
77    }
78
79    return ps_free(malloc_ops, sizeof(*cookie), cookie);
80}
81
82int ps_fdt_walk_registers(ps_io_fdt_t *io_fdt, ps_fdt_cookie_t *cookie, reg_walk_cb_fn_t callback, void *token)
83{
84    if (!io_fdt || !callback || !cookie) {
85        return -EINVAL;
86    }
87
88    char *dtb_blob = ps_io_fdt_get(io_fdt);
89    if (!dtb_blob) {
90        return -EINVAL;
91    }
92
93    int node_offset = cookie->node_offset;
94
95    /* NOTE: apparently fdt_parent_offset is expensive to use,
96     * maybe manipulate the path to get the parent's node path instead? */
97    int parent_offset = fdt_parent_offset(dtb_blob, node_offset);
98    if (parent_offset < 0) {
99        return parent_offset;
100    }
101
102    /* get the number of address and size cells */
103    int num_address_cells = fdt_address_cells(dtb_blob, parent_offset);
104    if (num_address_cells < 0) {
105        return num_address_cells;
106    }
107    int num_size_cells = fdt_size_cells(dtb_blob, parent_offset);
108    if (num_size_cells < 0) {
109        return num_size_cells;
110    }
111
112    if (num_address_cells == 0 || num_size_cells == 0) {
113        /* this isn't really a standard device, so we just return */
114        return -FDT_ERR_NOTFOUND;
115    }
116
117    int prop_len = 0;
118    const void *reg_prop = fdt_getprop(dtb_blob, node_offset, "reg", &prop_len);
119    if (!reg_prop) {
120        /* The error is written to the variable passed in */
121        return prop_len;
122    }
123    int total_cells = prop_len / sizeof(uint32_t);
124
125    /* sanity check */
126    assert(total_cells % (num_address_cells + num_size_cells) == 0);
127
128    int stride = num_address_cells + num_size_cells;
129    int num_regs = total_cells / stride;
130
131    for (int i = 0; i < num_regs; i++) {
132        pmem_region_t curr_pmem = {0};
133        const void *curr = reg_prop + (i * stride * sizeof(uint32_t));
134        curr_pmem.type = PMEM_TYPE_DEVICE;
135        curr_pmem.base_addr = READ_CELL(num_address_cells, curr, 0);
136        curr_pmem.length = READ_CELL(num_size_cells, curr, num_address_cells);
137        int error = callback(curr_pmem, i, num_regs, token);
138        if (error) {
139            return error;
140        }
141    }
142
143    return 0;
144}
145
146static inline ps_irqchip_t **find_compatible_irq_controller(char *dtb_blob, int intr_controller_offset)
147{
148    for (ps_irqchip_t **irqchip = __start__ps_irqchips; irqchip < __stop__ps_irqchips; irqchip++) {
149        for (char **compatible_str = (*irqchip)->compatible_list; *compatible_str != NULL; compatible_str++) {
150            if (fdt_node_check_compatible(dtb_blob, intr_controller_offset, *compatible_str) == 0) {
151                return irqchip;
152            }
153        }
154    }
155
156    return NULL;
157}
158
159int ps_fdt_walk_irqs(ps_io_fdt_t *io_fdt, ps_fdt_cookie_t *cookie, irq_walk_cb_fn_t callback, void *token)
160{
161    if (!io_fdt || !callback || !cookie) {
162        return -EINVAL;
163    }
164
165    char *dtb_blob = ps_io_fdt_get(io_fdt);
166    if (!dtb_blob) {
167        return -EINVAL;
168    }
169
170    int node_offset = cookie->node_offset;
171
172    /* check that this node actually has interrupts */
173    const void *intr_addr = fdt_getprop(dtb_blob, node_offset, "interrupts", NULL);
174    if (!intr_addr) {
175        intr_addr = fdt_getprop(dtb_blob, node_offset, "interrupts-extended", NULL);
176        if (!intr_addr) {
177            return -FDT_ERR_NOTFOUND;
178        }
179    }
180
181    /* get the interrupt controller of the node */
182    int curr_offset = node_offset;
183    bool found_controller = false;
184    const void *intr_parent_prop;
185    while (curr_offset >= 0 && !found_controller) {
186        intr_parent_prop = fdt_getprop(dtb_blob, curr_offset, "interrupt-parent", NULL);
187        if (!intr_parent_prop) {
188            /* move up a level
189             * NOTE: fdt_parent_offset is apparently expensive to use,
190             * maybe avoid using fdt_parent_offset and manipulate the path instead? */
191            curr_offset = fdt_parent_offset(dtb_blob, curr_offset);
192        } else {
193            found_controller = true;
194        }
195    }
196
197    if (!found_controller) {
198        /* something is really wrong with the FDT */
199        return -FDT_ERR_BADSTRUCTURE;
200    }
201
202    /* check if this controller is just a interrupt forwarder/not the root interrupt controller,
203     * and if it is, find the root controller
204     */
205    bool is_root_controller = false;
206    int root_intr_controller_offset = 0;
207    uint32_t root_intr_controller_phandle = 0;
208    while (!is_root_controller) {
209        uint32_t intr_controller_phandle = READ_CELL(1, intr_parent_prop, 0);
210        ZF_LOGF_IF(intr_controller_phandle == 0,
211                   "Failed to get the phandle of the interrupt controller of this node");
212        int intr_controller_offset = fdt_node_offset_by_phandle(dtb_blob, intr_controller_phandle);
213        ZF_LOGF_IF(intr_controller_offset < 0, "Failed to get the offset of the interrupt controller");
214        intr_parent_prop = fdt_getprop(dtb_blob, intr_controller_offset, "interrupt-parent", NULL);
215
216        /* The root interrupt controller node has one of two characteristics:
217         *      1. It has a 'interrupt-parent' property that points back to itself
218         *      2. It has no 'interrupt-parent' property
219         */
220        if (intr_parent_prop && intr_controller_phandle == READ_CELL(1, intr_parent_prop, 0)) {
221            is_root_controller = true;
222        } else if (!intr_parent_prop) {
223            is_root_controller = true;
224        }
225
226        if (is_root_controller) {
227            root_intr_controller_offset = intr_controller_offset;
228            root_intr_controller_phandle = intr_controller_phandle;
229        }
230    }
231
232    /* check the compatible string against our list of support interrupt controllers */
233    ps_irqchip_t **irqchip = find_compatible_irq_controller(dtb_blob, root_intr_controller_offset);
234    if (irqchip == NULL) {
235        ZF_LOGE("Could not find a parser for this particular interrupt controller");
236        return -ENOENT;
237    }
238
239    /* delegate to the interrupt controller specific code */
240    int error = (*irqchip)->parser_fn(dtb_blob, node_offset, root_intr_controller_phandle, callback, token);
241    if (error) {
242        ZF_LOGE("Failed to parse and walk the interrupt field");
243        return error;
244    }
245
246    return 0;
247}
248
249static int register_index_helper_walker(pmem_region_t pmem, unsigned curr_num, size_t num_regs, void *token)
250{
251    index_helper_token_t *helper_token = token;
252    if (helper_token->desired_offset >= num_regs) {
253        /* Bail early if we will never find it */
254        return -ENOENT;
255    }
256
257    if (helper_token->desired_offset == curr_num) {
258        void *ret_addr = ps_pmem_map(helper_token->io_ops, pmem, false, PS_MEM_NORMAL);
259        if (ret_addr == NULL) {
260            return -ENOMEM;
261        } else {
262            helper_token->mapped_addr = ret_addr;
263            helper_token->region = pmem;
264        }
265    }
266
267    return 0;
268}
269
270void *ps_fdt_index_map_register(ps_io_ops_t *io_ops, ps_fdt_cookie_t *cookie, unsigned offset,
271                                pmem_region_t *ret_pmem)
272{
273    if (io_ops == NULL) {
274        ZF_LOGE("io_ops is NULL!");
275        return NULL;
276    }
277
278    if (cookie == NULL) {
279        ZF_LOGE("cookie is NULL");
280        return NULL;
281    }
282
283    index_helper_token_t token = { .io_ops = io_ops, .desired_offset = offset };
284
285    int error = ps_fdt_walk_registers(&io_ops->io_fdt, cookie, register_index_helper_walker,
286                                      &token);
287    if (error) {
288        return NULL;
289    }
290
291    if (ret_pmem) {
292        *ret_pmem = token.region;
293    }
294
295    return token.mapped_addr;
296}
297
298static int irq_index_helper_walker(ps_irq_t irq, unsigned curr_num, size_t num_irqs, void *token)
299{
300    index_helper_token_t *helper_token = token;
301    if (helper_token->desired_offset >= num_irqs) {
302        /* Bail early if we will never find it */
303        return -ENOENT;
304    }
305
306    if (helper_token->desired_offset == curr_num) {
307        irq_id_t registered_id = ps_irq_register(&helper_token->io_ops->irq_ops,
308                                                 irq, helper_token->irq_callback,
309                                                 helper_token->irq_callback_data);
310        if (registered_id >= 0) {
311            helper_token->irq_id = registered_id;
312        } else {
313            /* Bail on error */
314            return registered_id;
315        }
316    }
317
318    return 0;
319}
320
321irq_id_t ps_fdt_index_register_irq(ps_io_ops_t *io_ops, ps_fdt_cookie_t *cookie, unsigned offset,
322                                   irq_callback_fn_t irq_callback, void *irq_callback_data)
323{
324    if (io_ops == NULL) {
325        ZF_LOGE("io_ops is NULL!");
326        return -EINVAL;
327    }
328
329    if (cookie == NULL) {
330        ZF_LOGE("cookie is NULL");
331        return -EINVAL;
332    }
333
334    index_helper_token_t token = { .io_ops = io_ops, .desired_offset = offset,
335                                   .irq_callback = irq_callback,
336                                   .irq_callback_data = irq_callback_data
337                                 };
338
339    int error = ps_fdt_walk_irqs(&io_ops->io_fdt, cookie, irq_index_helper_walker, &token);
340    if (error) {
341        assert(error <= PS_INVALID_IRQ_ID);
342        return error;
343    }
344
345    return token.irq_id;
346}
347