Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * This file implements handling of
0004  * Arm Generic Diagnostic Dump and Reset Interface table (AGDI)
0005  *
0006  * Copyright (c) 2022, Ampere Computing LLC
0007  */
0008 
0009 #define pr_fmt(fmt) "ACPI: AGDI: " fmt
0010 
0011 #include <linux/acpi.h>
0012 #include <linux/acpi_agdi.h>
0013 #include <linux/arm_sdei.h>
0014 #include <linux/io.h>
0015 #include <linux/kernel.h>
0016 #include <linux/platform_device.h>
0017 
0018 struct agdi_data {
0019     int sdei_event;
0020 };
0021 
0022 static int agdi_sdei_handler(u32 sdei_event, struct pt_regs *regs, void *arg)
0023 {
0024     nmi_panic(regs, "Arm Generic Diagnostic Dump and Reset SDEI event issued");
0025     return 0;
0026 }
0027 
0028 static int agdi_sdei_probe(struct platform_device *pdev,
0029                struct agdi_data *adata)
0030 {
0031     int err;
0032 
0033     err = sdei_event_register(adata->sdei_event, agdi_sdei_handler, pdev);
0034     if (err) {
0035         dev_err(&pdev->dev, "Failed to register for SDEI event %d",
0036             adata->sdei_event);
0037         return err;
0038     }
0039 
0040     err = sdei_event_enable(adata->sdei_event);
0041     if (err)  {
0042         sdei_event_unregister(adata->sdei_event);
0043         dev_err(&pdev->dev, "Failed to enable event %d\n",
0044             adata->sdei_event);
0045         return err;
0046     }
0047 
0048     return 0;
0049 }
0050 
0051 static int agdi_probe(struct platform_device *pdev)
0052 {
0053     struct agdi_data *adata = dev_get_platdata(&pdev->dev);
0054 
0055     if (!adata)
0056         return -EINVAL;
0057 
0058     return agdi_sdei_probe(pdev, adata);
0059 }
0060 
0061 static int agdi_remove(struct platform_device *pdev)
0062 {
0063     struct agdi_data *adata = dev_get_platdata(&pdev->dev);
0064     int err, i;
0065 
0066     err = sdei_event_disable(adata->sdei_event);
0067     if (err)
0068         return err;
0069 
0070     for (i = 0; i < 3; i++) {
0071         err = sdei_event_unregister(adata->sdei_event);
0072         if (err != -EINPROGRESS)
0073             break;
0074 
0075         schedule();
0076     }
0077 
0078     return err;
0079 }
0080 
0081 static struct platform_driver agdi_driver = {
0082     .driver = {
0083         .name = "agdi",
0084     },
0085     .probe = agdi_probe,
0086     .remove = agdi_remove,
0087 };
0088 
0089 void __init acpi_agdi_init(void)
0090 {
0091     struct acpi_table_agdi *agdi_table;
0092     struct agdi_data pdata;
0093     struct platform_device *pdev;
0094     acpi_status status;
0095 
0096     status = acpi_get_table(ACPI_SIG_AGDI, 0,
0097                 (struct acpi_table_header **) &agdi_table);
0098     if (ACPI_FAILURE(status))
0099         return;
0100 
0101     if (agdi_table->flags & ACPI_AGDI_SIGNALING_MODE) {
0102         pr_warn("Interrupt signaling is not supported");
0103         goto err_put_table;
0104     }
0105 
0106     pdata.sdei_event = agdi_table->sdei_event;
0107 
0108     pdev = platform_device_register_data(NULL, "agdi", 0, &pdata, sizeof(pdata));
0109     if (IS_ERR(pdev))
0110         goto err_put_table;
0111 
0112     if (platform_driver_register(&agdi_driver))
0113         platform_device_unregister(pdev);
0114 
0115 err_put_table:
0116     acpi_put_table((struct acpi_table_header *)agdi_table);
0117 }