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