1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Xilinx Event Management Driver 4 * 5 * Copyright (C) 2021 Xilinx, Inc. 6 * 7 * Abhyuday Godhasara <abhyuday.godhasara@xilinx.com> 8 */ 9 10 #include <linux/cpuhotplug.h> 11 #include <linux/firmware/xlnx-event-manager.h> 12 #include <linux/firmware/xlnx-zynqmp.h> 13 #include <linux/hashtable.h> 14 #include <linux/interrupt.h> 15 #include <linux/irq.h> 16 #include <linux/irqdomain.h> 17 #include <linux/module.h> 18 #include <linux/of_irq.h> 19 #include <linux/platform_device.h> 20 #include <linux/slab.h> 21 22 static DEFINE_PER_CPU_READ_MOSTLY(int, cpu_number1); 23 24 static int virq_sgi; 25 static int event_manager_availability = -EACCES; 26 27 /* SGI number used for Event management driver */ 28 #define XLNX_EVENT_SGI_NUM (15) 29 30 /* Max number of driver can register for same event */ 31 #define MAX_DRIVER_PER_EVENT (10U) 32 33 /* Max HashMap Order for PM API feature check (1<<7 = 128) */ 34 #define REGISTERED_DRIVER_MAX_ORDER (7) 35 36 #define MAX_BITS (32U) /* Number of bits available for error mask */ 37 38 #define FIRMWARE_VERSION_MASK (0xFFFFU) 39 #define REGISTER_NOTIFIER_FIRMWARE_VERSION (2U) 40 41 static DEFINE_HASHTABLE(reg_driver_map, REGISTERED_DRIVER_MAX_ORDER); 42 static int sgi_num = XLNX_EVENT_SGI_NUM; 43 44 /** 45 * struct registered_event_data - Registered Event Data. 46 * @key: key is the combine id(Node-Id | Event-Id) of type u64 47 * where upper u32 for Node-Id and lower u32 for Event-Id, 48 * And this used as key to index into hashmap. 49 * @agent_data: Data passed back to handler function. 50 * @cb_type: Type of Api callback, like PM_NOTIFY_CB, etc. 51 * @eve_cb: Function pointer to store the callback function. 52 * @wake: If this flag set, firmware will wakeup processor if is 53 * in sleep or power down state. 54 * @hentry: hlist_node that hooks this entry into hashtable. 55 */ 56 struct registered_event_data { 57 u64 key; 58 enum pm_api_cb_id cb_type; 59 void *agent_data; 60 61 event_cb_func_t eve_cb; 62 bool wake; 63 struct hlist_node hentry; 64 }; 65 66 static bool xlnx_is_error_event(const u32 node_id) 67 { 68 if (node_id == EVENT_ERROR_PMC_ERR1 || 69 node_id == EVENT_ERROR_PMC_ERR2 || 70 node_id == EVENT_ERROR_PSM_ERR1 || 71 node_id == EVENT_ERROR_PSM_ERR2) 72 return true; 73 74 return false; 75 } 76 77 static int xlnx_add_cb_for_notify_event(const u32 node_id, const u32 event, const bool wake, 78 event_cb_func_t cb_fun, void *data) 79 { 80 u64 key = 0; 81 struct registered_event_data *eve_data; 82 83 key = ((u64)node_id << 32U) | (u64)event; 84 /* Check for existing entry in hash table for given key id */ 85 hash_for_each_possible(reg_driver_map, eve_data, hentry, key) { 86 if (eve_data->key == key) { 87 pr_err("Found as already registered\n"); 88 return -EINVAL; 89 } 90 } 91 92 /* Add new entry if not present */ 93 eve_data = kmalloc(sizeof(*eve_data), GFP_KERNEL); 94 if (!eve_data) 95 return -ENOMEM; 96 97 eve_data->key = key; 98 eve_data->cb_type = PM_NOTIFY_CB; 99 eve_data->eve_cb = cb_fun; 100 eve_data->wake = wake; 101 eve_data->agent_data = data; 102 103 hash_add(reg_driver_map, &eve_data->hentry, key); 104 105 return 0; 106 } 107 108 static int xlnx_add_cb_for_suspend(event_cb_func_t cb_fun, void *data) 109 { 110 struct registered_event_data *eve_data; 111 112 /* Check for existing entry in hash table for given cb_type */ 113 hash_for_each_possible(reg_driver_map, eve_data, hentry, PM_INIT_SUSPEND_CB) { 114 if (eve_data->cb_type == PM_INIT_SUSPEND_CB) { 115 pr_err("Found as already registered\n"); 116 return -EINVAL; 117 } 118 } 119 120 /* Add new entry if not present */ 121 eve_data = kmalloc(sizeof(*eve_data), GFP_KERNEL); 122 if (!eve_data) 123 return -ENOMEM; 124 125 eve_data->key = 0; 126 eve_data->cb_type = PM_INIT_SUSPEND_CB; 127 eve_data->eve_cb = cb_fun; 128 eve_data->agent_data = data; 129 130 hash_add(reg_driver_map, &eve_data->hentry, PM_INIT_SUSPEND_CB); 131 132 return 0; 133 } 134 135 static int xlnx_remove_cb_for_suspend(event_cb_func_t cb_fun) 136 { 137 bool is_callback_found = false; 138 struct registered_event_data *eve_data; 139 140 /* Check for existing entry in hash table for given cb_type */ 141 hash_for_each_possible(reg_driver_map, eve_data, hentry, PM_INIT_SUSPEND_CB) { 142 if (eve_data->cb_type == PM_INIT_SUSPEND_CB && 143 eve_data->eve_cb == cb_fun) { 144 is_callback_found = true; 145 /* remove an object from a hashtable */ 146 hash_del(&eve_data->hentry); 147 kfree(eve_data); 148 } 149 } 150 if (!is_callback_found) { 151 pr_warn("Didn't find any registered callback for suspend event\n"); 152 return -EINVAL; 153 } 154 155 return 0; 156 } 157 158 static int xlnx_remove_cb_for_notify_event(const u32 node_id, const u32 event, 159 event_cb_func_t cb_fun) 160 { 161 bool is_callback_found = false; 162 struct registered_event_data *eve_data; 163 u64 key = ((u64)node_id << 32U) | (u64)event; 164 165 /* Check for existing entry in hash table for given key id */ 166 hash_for_each_possible(reg_driver_map, eve_data, hentry, key) { 167 if (eve_data->key == key && 168 eve_data->eve_cb == cb_fun) { 169 is_callback_found = true; 170 /* remove an object from a hashtable */ 171 hash_del(&eve_data->hentry); 172 kfree(eve_data); 173 } 174 } 175 if (!is_callback_found) { 176 pr_warn("Didn't find any registered callback for 0x%x 0x%x\n", 177 node_id, event); 178 return -EINVAL; 179 } 180 181 return 0; 182 } 183 184 /** 185 * xlnx_register_event() - Register for the event. 186 * @cb_type: Type of callback from pm_api_cb_id, 187 * PM_NOTIFY_CB - for Error Events, 188 * PM_INIT_SUSPEND_CB - for suspend callback. 189 * @node_id: Node-Id related to event. 190 * @event: Event Mask for the Error Event. 191 * @wake: Flag specifying whether the subsystem should be woken upon 192 * event notification. 193 * @cb_fun: Function pointer to store the callback function. 194 * @data: Pointer for the driver instance. 195 * 196 * Return: Returns 0 on successful registration else error code. 197 */ 198 int xlnx_register_event(const enum pm_api_cb_id cb_type, const u32 node_id, const u32 event, 199 const bool wake, event_cb_func_t cb_fun, void *data) 200 { 201 int ret = 0; 202 u32 eve; 203 int pos; 204 205 if (event_manager_availability) 206 return event_manager_availability; 207 208 if (cb_type != PM_NOTIFY_CB && cb_type != PM_INIT_SUSPEND_CB) { 209 pr_err("%s() Unsupported Callback 0x%x\n", __func__, cb_type); 210 return -EINVAL; 211 } 212 213 if (!cb_fun) 214 return -EFAULT; 215 216 if (cb_type == PM_INIT_SUSPEND_CB) { 217 ret = xlnx_add_cb_for_suspend(cb_fun, data); 218 } else { 219 if (!xlnx_is_error_event(node_id)) { 220 /* Add entry for Node-Id/Event in hash table */ 221 ret = xlnx_add_cb_for_notify_event(node_id, event, wake, cb_fun, data); 222 } else { 223 /* Add into Hash table */ 224 for (pos = 0; pos < MAX_BITS; pos++) { 225 eve = event & (1 << pos); 226 if (!eve) 227 continue; 228 229 /* Add entry for Node-Id/Eve in hash table */ 230 ret = xlnx_add_cb_for_notify_event(node_id, eve, wake, cb_fun, 231 data); 232 /* Break the loop if got error */ 233 if (ret) 234 break; 235 } 236 if (ret) { 237 /* Skip the Event for which got the error */ 238 pos--; 239 /* Remove registered(during this call) event from hash table */ 240 for ( ; pos >= 0; pos--) { 241 eve = event & (1 << pos); 242 if (!eve) 243 continue; 244 xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun); 245 } 246 } 247 } 248 249 if (ret) { 250 pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, node_id, 251 event, ret); 252 return ret; 253 } 254 255 /* Register for Node-Id/Event combination in firmware */ 256 ret = zynqmp_pm_register_notifier(node_id, event, wake, true); 257 if (ret) { 258 pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, node_id, 259 event, ret); 260 /* Remove already registered event from hash table */ 261 if (xlnx_is_error_event(node_id)) { 262 for (pos = 0; pos < MAX_BITS; pos++) { 263 eve = event & (1 << pos); 264 if (!eve) 265 continue; 266 xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun); 267 } 268 } else { 269 xlnx_remove_cb_for_notify_event(node_id, event, cb_fun); 270 } 271 return ret; 272 } 273 } 274 275 return ret; 276 } 277 EXPORT_SYMBOL_GPL(xlnx_register_event); 278 279 /** 280 * xlnx_unregister_event() - Unregister for the event. 281 * @cb_type: Type of callback from pm_api_cb_id, 282 * PM_NOTIFY_CB - for Error Events, 283 * PM_INIT_SUSPEND_CB - for suspend callback. 284 * @node_id: Node-Id related to event. 285 * @event: Event Mask for the Error Event. 286 * @cb_fun: Function pointer of callback function. 287 * 288 * Return: Returns 0 on successful unregistration else error code. 289 */ 290 int xlnx_unregister_event(const enum pm_api_cb_id cb_type, const u32 node_id, const u32 event, 291 event_cb_func_t cb_fun) 292 { 293 int ret; 294 u32 eve, pos; 295 296 if (event_manager_availability) 297 return event_manager_availability; 298 299 if (cb_type != PM_NOTIFY_CB && cb_type != PM_INIT_SUSPEND_CB) { 300 pr_err("%s() Unsupported Callback 0x%x\n", __func__, cb_type); 301 return -EINVAL; 302 } 303 304 if (!cb_fun) 305 return -EFAULT; 306 307 if (cb_type == PM_INIT_SUSPEND_CB) { 308 ret = xlnx_remove_cb_for_suspend(cb_fun); 309 } else { 310 /* Remove Node-Id/Event from hash table */ 311 if (!xlnx_is_error_event(node_id)) { 312 xlnx_remove_cb_for_notify_event(node_id, event, cb_fun); 313 } else { 314 for (pos = 0; pos < MAX_BITS; pos++) { 315 eve = event & (1 << pos); 316 if (!eve) 317 continue; 318 319 xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun); 320 } 321 } 322 323 /* Un-register for Node-Id/Event combination */ 324 ret = zynqmp_pm_register_notifier(node_id, event, false, false); 325 if (ret) { 326 pr_err("%s() failed for 0x%x and 0x%x: %d\n", 327 __func__, node_id, event, ret); 328 return ret; 329 } 330 } 331 332 return ret; 333 } 334 EXPORT_SYMBOL_GPL(xlnx_unregister_event); 335 336 static void xlnx_call_suspend_cb_handler(const u32 *payload) 337 { 338 bool is_callback_found = false; 339 struct registered_event_data *eve_data; 340 u32 cb_type = payload[0]; 341 342 /* Check for existing entry in hash table for given cb_type */ 343 hash_for_each_possible(reg_driver_map, eve_data, hentry, cb_type) { 344 if (eve_data->cb_type == cb_type) { 345 eve_data->eve_cb(&payload[0], eve_data->agent_data); 346 is_callback_found = true; 347 } 348 } 349 if (!is_callback_found) 350 pr_warn("Didn't find any registered callback for suspend event\n"); 351 } 352 353 static void xlnx_call_notify_cb_handler(const u32 *payload) 354 { 355 bool is_callback_found = false; 356 struct registered_event_data *eve_data; 357 u64 key = ((u64)payload[1] << 32U) | (u64)payload[2]; 358 int ret; 359 360 /* Check for existing entry in hash table for given key id */ 361 hash_for_each_possible(reg_driver_map, eve_data, hentry, key) { 362 if (eve_data->key == key) { 363 eve_data->eve_cb(&payload[0], eve_data->agent_data); 364 is_callback_found = true; 365 366 /* re register with firmware to get future events */ 367 ret = zynqmp_pm_register_notifier(payload[1], payload[2], 368 eve_data->wake, true); 369 if (ret) { 370 pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, 371 payload[1], payload[2], ret); 372 /* Remove already registered event from hash table */ 373 xlnx_remove_cb_for_notify_event(payload[1], payload[2], 374 eve_data->eve_cb); 375 } 376 } 377 } 378 if (!is_callback_found) 379 pr_warn("Didn't find any registered callback for 0x%x 0x%x\n", 380 payload[1], payload[2]); 381 } 382 383 static void xlnx_get_event_callback_data(u32 *buf) 384 { 385 zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf); 386 } 387 388 static irqreturn_t xlnx_event_handler(int irq, void *dev_id) 389 { 390 u32 cb_type, node_id, event, pos; 391 u32 payload[CB_MAX_PAYLOAD_SIZE] = {0}; 392 u32 event_data[CB_MAX_PAYLOAD_SIZE] = {0}; 393 394 /* Get event data */ 395 xlnx_get_event_callback_data(payload); 396 397 /* First element is callback type, others are callback arguments */ 398 cb_type = payload[0]; 399 400 if (cb_type == PM_NOTIFY_CB) { 401 node_id = payload[1]; 402 event = payload[2]; 403 if (!xlnx_is_error_event(node_id)) { 404 xlnx_call_notify_cb_handler(payload); 405 } else { 406 /* 407 * Each call back function expecting payload as an input arguments. 408 * We can get multiple error events as in one call back through error 409 * mask. So payload[2] may can contain multiple error events. 410 * In reg_driver_map database we store data in the combination of single 411 * node_id-error combination. 412 * So coping the payload message into event_data and update the 413 * event_data[2] with Error Mask for single error event and use 414 * event_data as input argument for registered call back function. 415 * 416 */ 417 memcpy(event_data, payload, (4 * CB_MAX_PAYLOAD_SIZE)); 418 /* Support Multiple Error Event */ 419 for (pos = 0; pos < MAX_BITS; pos++) { 420 if ((0 == (event & (1 << pos)))) 421 continue; 422 event_data[2] = (event & (1 << pos)); 423 xlnx_call_notify_cb_handler(event_data); 424 } 425 } 426 } else if (cb_type == PM_INIT_SUSPEND_CB) { 427 xlnx_call_suspend_cb_handler(payload); 428 } else { 429 pr_err("%s() Unsupported Callback %d\n", __func__, cb_type); 430 } 431 432 return IRQ_HANDLED; 433 } 434 435 static int xlnx_event_cpuhp_start(unsigned int cpu) 436 { 437 enable_percpu_irq(virq_sgi, IRQ_TYPE_NONE); 438 439 return 0; 440 } 441 442 static int xlnx_event_cpuhp_down(unsigned int cpu) 443 { 444 disable_percpu_irq(virq_sgi); 445 446 return 0; 447 } 448 449 static void xlnx_disable_percpu_irq(void *data) 450 { 451 disable_percpu_irq(virq_sgi); 452 } 453 454 static int xlnx_event_init_sgi(struct platform_device *pdev) 455 { 456 int ret = 0; 457 int cpu = smp_processor_id(); 458 /* 459 * IRQ related structures are used for the following: 460 * for each SGI interrupt ensure its mapped by GIC IRQ domain 461 * and that each corresponding linux IRQ for the HW IRQ has 462 * a handler for when receiving an interrupt from the remote 463 * processor. 464 */ 465 struct irq_domain *domain; 466 struct irq_fwspec sgi_fwspec; 467 struct device_node *interrupt_parent = NULL; 468 struct device *parent = pdev->dev.parent; 469 470 /* Find GIC controller to map SGIs. */ 471 interrupt_parent = of_irq_find_parent(parent->of_node); 472 if (!interrupt_parent) { 473 dev_err(&pdev->dev, "Failed to find property for Interrupt parent\n"); 474 return -EINVAL; 475 } 476 477 /* Each SGI needs to be associated with GIC's IRQ domain. */ 478 domain = irq_find_host(interrupt_parent); 479 of_node_put(interrupt_parent); 480 481 /* Each mapping needs GIC domain when finding IRQ mapping. */ 482 sgi_fwspec.fwnode = domain->fwnode; 483 484 /* 485 * When irq domain looks at mapping each arg is as follows: 486 * 3 args for: interrupt type (SGI), interrupt # (set later), type 487 */ 488 sgi_fwspec.param_count = 1; 489 490 /* Set SGI's hwirq */ 491 sgi_fwspec.param[0] = sgi_num; 492 virq_sgi = irq_create_fwspec_mapping(&sgi_fwspec); 493 494 per_cpu(cpu_number1, cpu) = cpu; 495 ret = request_percpu_irq(virq_sgi, xlnx_event_handler, "xlnx_event_mgmt", 496 &cpu_number1); 497 WARN_ON(ret); 498 if (ret) { 499 irq_dispose_mapping(virq_sgi); 500 return ret; 501 } 502 503 irq_to_desc(virq_sgi); 504 irq_set_status_flags(virq_sgi, IRQ_PER_CPU); 505 506 return ret; 507 } 508 509 static void xlnx_event_cleanup_sgi(struct platform_device *pdev) 510 { 511 int cpu = smp_processor_id(); 512 513 per_cpu(cpu_number1, cpu) = cpu; 514 515 cpuhp_remove_state(CPUHP_AP_ONLINE_DYN); 516 517 on_each_cpu(xlnx_disable_percpu_irq, NULL, 1); 518 519 irq_clear_status_flags(virq_sgi, IRQ_PER_CPU); 520 free_percpu_irq(virq_sgi, &cpu_number1); 521 irq_dispose_mapping(virq_sgi); 522 } 523 524 static int xlnx_event_manager_probe(struct platform_device *pdev) 525 { 526 int ret; 527 528 ret = zynqmp_pm_feature(PM_REGISTER_NOTIFIER); 529 if (ret < 0) { 530 dev_err(&pdev->dev, "Feature check failed with %d\n", ret); 531 return ret; 532 } 533 534 if ((ret & FIRMWARE_VERSION_MASK) < 535 REGISTER_NOTIFIER_FIRMWARE_VERSION) { 536 dev_err(&pdev->dev, "Register notifier version error. Expected Firmware: v%d - Found: v%d\n", 537 REGISTER_NOTIFIER_FIRMWARE_VERSION, 538 ret & FIRMWARE_VERSION_MASK); 539 return -EOPNOTSUPP; 540 } 541 542 /* Initialize the SGI */ 543 ret = xlnx_event_init_sgi(pdev); 544 if (ret) { 545 dev_err(&pdev->dev, "SGI Init has been failed with %d\n", ret); 546 return ret; 547 } 548 549 /* Setup function for the CPU hot-plug cases */ 550 cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "soc/event:starting", 551 xlnx_event_cpuhp_start, xlnx_event_cpuhp_down); 552 553 ret = zynqmp_pm_invoke_fn(PM_IOCTL, 0, IOCTL_REGISTER_SGI, sgi_num, 554 0, NULL); 555 if (ret) { 556 dev_err(&pdev->dev, "SGI %d Registration over TF-A failed with %d\n", sgi_num, ret); 557 xlnx_event_cleanup_sgi(pdev); 558 return ret; 559 } 560 561 event_manager_availability = 0; 562 563 dev_info(&pdev->dev, "SGI %d Registered over TF-A\n", sgi_num); 564 dev_info(&pdev->dev, "Xilinx Event Management driver probed\n"); 565 566 return ret; 567 } 568 569 static int xlnx_event_manager_remove(struct platform_device *pdev) 570 { 571 int i; 572 struct registered_event_data *eve_data; 573 struct hlist_node *tmp; 574 int ret; 575 576 hash_for_each_safe(reg_driver_map, i, tmp, eve_data, hentry) { 577 hash_del(&eve_data->hentry); 578 kfree(eve_data); 579 } 580 581 ret = zynqmp_pm_invoke_fn(PM_IOCTL, 0, IOCTL_REGISTER_SGI, 0, 1, NULL); 582 if (ret) 583 dev_err(&pdev->dev, "SGI unregistration over TF-A failed with %d\n", ret); 584 585 xlnx_event_cleanup_sgi(pdev); 586 587 event_manager_availability = -EACCES; 588 589 return ret; 590 } 591 592 static struct platform_driver xlnx_event_manager_driver = { 593 .probe = xlnx_event_manager_probe, 594 .remove = xlnx_event_manager_remove, 595 .driver = { 596 .name = "xlnx_event_manager", 597 }, 598 }; 599 module_param(sgi_num, uint, 0); 600 module_platform_driver(xlnx_event_manager_driver); 601