1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * This file implements handling of 4 * Arm Generic Diagnostic Dump and Reset Interface table (AGDI) 5 * 6 * Copyright (c) 2022, Ampere Computing LLC 7 */ 8 9 #define pr_fmt(fmt) "ACPI: AGDI: " fmt 10 11 #include <linux/acpi.h> 12 #include <linux/arm_sdei.h> 13 #include <linux/io.h> 14 #include <linux/kernel.h> 15 #include <linux/platform_device.h> 16 #include "init.h" 17 18 struct agdi_data { 19 unsigned char flags; /* AGDI Signaling Mode */ 20 int sdei_event; 21 unsigned int gsiv; 22 bool use_nmi; 23 int irq; 24 }; 25 26 static int agdi_sdei_handler(u32 sdei_event, struct pt_regs *regs, void *arg) 27 { 28 nmi_panic(regs, "Arm Generic Diagnostic Dump and Reset SDEI event issued"); 29 return 0; 30 } 31 32 static int agdi_sdei_probe(struct platform_device *pdev, 33 struct agdi_data *adata) 34 { 35 int err; 36 37 err = sdei_event_register(adata->sdei_event, agdi_sdei_handler, pdev); 38 if (err) { 39 dev_err(&pdev->dev, "Failed to register for SDEI event %d", 40 adata->sdei_event); 41 return err; 42 } 43 44 err = sdei_event_enable(adata->sdei_event); 45 if (err) { 46 sdei_event_unregister(adata->sdei_event); 47 dev_err(&pdev->dev, "Failed to enable event %d\n", 48 adata->sdei_event); 49 return err; 50 } 51 52 return 0; 53 } 54 55 static irqreturn_t agdi_interrupt_handler_nmi(int irq, void *dev_id) 56 { 57 nmi_panic(NULL, "Arm Generic Diagnostic Dump and Reset NMI Interrupt event issued\n"); 58 return IRQ_HANDLED; 59 } 60 61 static irqreturn_t agdi_interrupt_handler_irq(int irq, void *dev_id) 62 { 63 panic("Arm Generic Diagnostic Dump and Reset Interrupt event issued\n"); 64 return IRQ_HANDLED; 65 } 66 67 static int agdi_interrupt_probe(struct platform_device *pdev, 68 struct agdi_data *adata) 69 { 70 unsigned long irq_flags; 71 int ret; 72 int irq; 73 74 irq = acpi_register_gsi(NULL, adata->gsiv, ACPI_EDGE_SENSITIVE, ACPI_ACTIVE_HIGH); 75 if (irq < 0) { 76 dev_err(&pdev->dev, "cannot register GSI#%d (%d)\n", adata->gsiv, irq); 77 return irq; 78 } 79 80 irq_flags = IRQF_PERCPU | IRQF_NOBALANCING | IRQF_NO_AUTOEN | 81 IRQF_NO_THREAD; 82 /* try NMI first */ 83 ret = request_nmi(irq, &agdi_interrupt_handler_nmi, irq_flags, 84 "agdi_interrupt_nmi", NULL); 85 if (!ret) { 86 enable_nmi(irq); 87 adata->irq = irq; 88 adata->use_nmi = true; 89 return 0; 90 } 91 92 /* Then try normal interrupt */ 93 ret = request_irq(irq, &agdi_interrupt_handler_irq, 94 irq_flags, "agdi_interrupt_irq", NULL); 95 if (ret) { 96 dev_err(&pdev->dev, "cannot register IRQ %d\n", ret); 97 acpi_unregister_gsi(adata->gsiv); 98 return ret; 99 } 100 enable_irq(irq); 101 adata->irq = irq; 102 103 return 0; 104 } 105 106 static int agdi_probe(struct platform_device *pdev) 107 { 108 struct agdi_data *adata = dev_get_platdata(&pdev->dev); 109 110 if (!adata) 111 return -EINVAL; 112 113 if (adata->flags & ACPI_AGDI_SIGNALING_MODE) 114 return agdi_interrupt_probe(pdev, adata); 115 else 116 return agdi_sdei_probe(pdev, adata); 117 } 118 119 static void agdi_sdei_remove(struct platform_device *pdev, 120 struct agdi_data *adata) 121 { 122 int err, i; 123 124 err = sdei_event_disable(adata->sdei_event); 125 if (err) { 126 dev_err(&pdev->dev, "Failed to disable sdei-event #%d (%pe)\n", 127 adata->sdei_event, ERR_PTR(err)); 128 return; 129 } 130 131 for (i = 0; i < 3; i++) { 132 err = sdei_event_unregister(adata->sdei_event); 133 if (err != -EINPROGRESS) 134 break; 135 136 schedule(); 137 } 138 139 if (err) 140 dev_err(&pdev->dev, "Failed to unregister sdei-event #%d (%pe)\n", 141 adata->sdei_event, ERR_PTR(err)); 142 } 143 144 static void agdi_interrupt_remove(struct platform_device *pdev, 145 struct agdi_data *adata) 146 { 147 if (adata->irq == -1) 148 return; 149 150 if (adata->use_nmi) 151 free_nmi(adata->irq, NULL); 152 else 153 free_irq(adata->irq, NULL); 154 155 acpi_unregister_gsi(adata->gsiv); 156 } 157 158 static void agdi_remove(struct platform_device *pdev) 159 { 160 struct agdi_data *adata = dev_get_platdata(&pdev->dev); 161 162 if (adata->flags & ACPI_AGDI_SIGNALING_MODE) 163 agdi_interrupt_remove(pdev, adata); 164 else 165 agdi_sdei_remove(pdev, adata); 166 } 167 168 static struct platform_driver agdi_driver = { 169 .driver = { 170 .name = "agdi", 171 }, 172 .probe = agdi_probe, 173 .remove = agdi_remove, 174 }; 175 176 void __init acpi_agdi_init(void) 177 { 178 struct acpi_table_agdi *agdi_table; 179 struct agdi_data pdata = { 0 }; 180 struct platform_device *pdev; 181 acpi_status status; 182 183 status = acpi_get_table(ACPI_SIG_AGDI, 0, 184 (struct acpi_table_header **) &agdi_table); 185 if (ACPI_FAILURE(status)) 186 return; 187 188 if (agdi_table->flags & ACPI_AGDI_SIGNALING_MODE) 189 pdata.gsiv = agdi_table->gsiv; 190 else 191 pdata.sdei_event = agdi_table->sdei_event; 192 193 pdata.irq = -1; 194 pdata.flags = agdi_table->flags; 195 196 pdev = platform_device_register_data(NULL, "agdi", 0, &pdata, sizeof(pdata)); 197 if (IS_ERR(pdev)) 198 goto err_put_table; 199 200 if (platform_driver_register(&agdi_driver)) 201 platform_device_unregister(pdev); 202 203 err_put_table: 204 acpi_put_table((struct acpi_table_header *)agdi_table); 205 } 206