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 | int sdei_event; |
20 | }; |
21 | |
22 | static int agdi_sdei_handler(u32 sdei_event, struct pt_regs *regs, void *arg) |
23 | { |
24 | nmi_panic(regs, msg: "Arm Generic Diagnostic Dump and Reset SDEI event issued" ); |
25 | return 0; |
26 | } |
27 | |
28 | static int agdi_sdei_probe(struct platform_device *pdev, |
29 | struct agdi_data *adata) |
30 | { |
31 | int err; |
32 | |
33 | err = sdei_event_register(event_num: adata->sdei_event, cb: agdi_sdei_handler, arg: pdev); |
34 | if (err) { |
35 | dev_err(&pdev->dev, "Failed to register for SDEI event %d" , |
36 | adata->sdei_event); |
37 | return err; |
38 | } |
39 | |
40 | err = sdei_event_enable(event_num: adata->sdei_event); |
41 | if (err) { |
42 | sdei_event_unregister(event_num: adata->sdei_event); |
43 | dev_err(&pdev->dev, "Failed to enable event %d\n" , |
44 | adata->sdei_event); |
45 | return err; |
46 | } |
47 | |
48 | return 0; |
49 | } |
50 | |
51 | static int agdi_probe(struct platform_device *pdev) |
52 | { |
53 | struct agdi_data *adata = dev_get_platdata(dev: &pdev->dev); |
54 | |
55 | if (!adata) |
56 | return -EINVAL; |
57 | |
58 | return agdi_sdei_probe(pdev, adata); |
59 | } |
60 | |
61 | static void agdi_remove(struct platform_device *pdev) |
62 | { |
63 | struct agdi_data *adata = dev_get_platdata(dev: &pdev->dev); |
64 | int err, i; |
65 | |
66 | err = sdei_event_disable(event_num: adata->sdei_event); |
67 | if (err) { |
68 | dev_err(&pdev->dev, "Failed to disable sdei-event #%d (%pe)\n" , |
69 | adata->sdei_event, ERR_PTR(err)); |
70 | return; |
71 | } |
72 | |
73 | for (i = 0; i < 3; i++) { |
74 | err = sdei_event_unregister(event_num: adata->sdei_event); |
75 | if (err != -EINPROGRESS) |
76 | break; |
77 | |
78 | schedule(); |
79 | } |
80 | |
81 | if (err) |
82 | dev_err(&pdev->dev, "Failed to unregister sdei-event #%d (%pe)\n" , |
83 | adata->sdei_event, ERR_PTR(err)); |
84 | } |
85 | |
86 | static struct platform_driver agdi_driver = { |
87 | .driver = { |
88 | .name = "agdi" , |
89 | }, |
90 | .probe = agdi_probe, |
91 | .remove_new = agdi_remove, |
92 | }; |
93 | |
94 | void __init acpi_agdi_init(void) |
95 | { |
96 | struct acpi_table_agdi *agdi_table; |
97 | struct agdi_data pdata; |
98 | struct platform_device *pdev; |
99 | acpi_status status; |
100 | |
101 | status = acpi_get_table(ACPI_SIG_AGDI, instance: 0, |
102 | out_table: (struct acpi_table_header **) &agdi_table); |
103 | if (ACPI_FAILURE(status)) |
104 | return; |
105 | |
106 | if (agdi_table->flags & ACPI_AGDI_SIGNALING_MODE) { |
107 | pr_warn("Interrupt signaling is not supported" ); |
108 | goto err_put_table; |
109 | } |
110 | |
111 | pdata.sdei_event = agdi_table->sdei_event; |
112 | |
113 | pdev = platform_device_register_data(NULL, name: "agdi" , id: 0, data: &pdata, size: sizeof(pdata)); |
114 | if (IS_ERR(ptr: pdev)) |
115 | goto err_put_table; |
116 | |
117 | if (platform_driver_register(&agdi_driver)) |
118 | platform_device_unregister(pdev); |
119 | |
120 | err_put_table: |
121 | acpi_put_table(table: (struct acpi_table_header *)agdi_table); |
122 | } |
123 | |