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