Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/acpi/arm64/agdi.c
50774 views
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
207