linux/drivers/soc/xilinx/xlnx_event_manager.c
<<
>>
Prefs
   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
  22static DEFINE_PER_CPU_READ_MOSTLY(int, cpu_number1);
  23
  24static int virq_sgi;
  25static 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
  41static DEFINE_HASHTABLE(reg_driver_map, REGISTERED_DRIVER_MAX_ORDER);
  42static 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 */
  56struct 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
  66static 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
  77static 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
 108static 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
 135static 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
 158static 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 */
 198int 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}
 277EXPORT_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 */
 290int 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}
 334EXPORT_SYMBOL_GPL(xlnx_unregister_event);
 335
 336static 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
 353static 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
 383static void xlnx_get_event_callback_data(u32 *buf)
 384{
 385        zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
 386}
 387
 388static 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
 435static int xlnx_event_cpuhp_start(unsigned int cpu)
 436{
 437        enable_percpu_irq(virq_sgi, IRQ_TYPE_NONE);
 438
 439        return 0;
 440}
 441
 442static int xlnx_event_cpuhp_down(unsigned int cpu)
 443{
 444        disable_percpu_irq(virq_sgi);
 445
 446        return 0;
 447}
 448
 449static void xlnx_disable_percpu_irq(void *data)
 450{
 451        disable_percpu_irq(virq_sgi);
 452}
 453
 454static 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
 509static 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
 524static 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
 569static 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
 592static 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};
 599module_param(sgi_num, uint, 0);
 600module_platform_driver(xlnx_event_manager_driver);
 601