Commit | Line | Data |
---|---|---|
69472ffa SC |
1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* | |
3 | * Copyright (c) 2022 International Business Machines, Inc. | |
4 | */ | |
5 | ||
6 | #include <linux/bitops.h> | |
7 | #include <linux/kernel.h> | |
8 | #include <linux/limits.h> | |
9 | #include <linux/math.h> | |
10 | #include <linux/mod_devicetable.h> | |
11 | #include <linux/module.h> | |
12 | #include <linux/moduleparam.h> | |
13 | #include <linux/platform_device.h> | |
14 | #include <linux/time64.h> | |
15 | #include <linux/watchdog.h> | |
16 | ||
17 | #define DRV_NAME "pseries-wdt" | |
18 | ||
19 | /* | |
20 | * H_WATCHDOG Input | |
21 | * | |
22 | * R4: "flags": | |
23 | * | |
24 | * Bits 48-55: "operation" | |
25 | */ | |
26 | #define PSERIES_WDTF_OP_START 0x100UL /* start timer */ | |
27 | #define PSERIES_WDTF_OP_STOP 0x200UL /* stop timer */ | |
28 | #define PSERIES_WDTF_OP_QUERY 0x300UL /* query timer capabilities */ | |
29 | ||
30 | /* | |
31 | * Bits 56-63: "timeoutAction" (for "Start Watchdog" only) | |
32 | */ | |
33 | #define PSERIES_WDTF_ACTION_HARD_POWEROFF 0x1UL /* poweroff */ | |
34 | #define PSERIES_WDTF_ACTION_HARD_RESTART 0x2UL /* restart */ | |
35 | #define PSERIES_WDTF_ACTION_DUMP_RESTART 0x3UL /* dump + restart */ | |
36 | ||
37 | /* | |
38 | * H_WATCHDOG Output | |
39 | * | |
40 | * R3: Return code | |
41 | * | |
42 | * H_SUCCESS The operation completed. | |
43 | * | |
44 | * H_BUSY The hypervisor is too busy; retry the operation. | |
45 | * | |
46 | * H_PARAMETER The given "flags" are somehow invalid. Either the | |
47 | * "operation" or "timeoutAction" is invalid, or a | |
48 | * reserved bit is set. | |
49 | * | |
50 | * H_P2 The given "watchdogNumber" is zero or exceeds the | |
51 | * supported maximum value. | |
52 | * | |
53 | * H_P3 The given "timeoutInMs" is below the supported | |
54 | * minimum value. | |
55 | * | |
56 | * H_NOOP The given "watchdogNumber" is already stopped. | |
57 | * | |
58 | * H_HARDWARE The operation failed for ineffable reasons. | |
59 | * | |
60 | * H_FUNCTION The H_WATCHDOG hypercall is not supported by this | |
61 | * hypervisor. | |
62 | * | |
63 | * R4: | |
64 | * | |
65 | * - For the "Query Watchdog Capabilities" operation, a 64-bit | |
66 | * structure: | |
67 | */ | |
68 | #define PSERIES_WDTQ_MIN_TIMEOUT(cap) (((cap) >> 48) & 0xffff) | |
69 | #define PSERIES_WDTQ_MAX_NUMBER(cap) (((cap) >> 32) & 0xffff) | |
70 | ||
71 | static const unsigned long pseries_wdt_action[] = { | |
72 | [0] = PSERIES_WDTF_ACTION_HARD_POWEROFF, | |
73 | [1] = PSERIES_WDTF_ACTION_HARD_RESTART, | |
74 | [2] = PSERIES_WDTF_ACTION_DUMP_RESTART, | |
75 | }; | |
76 | ||
77 | #define WATCHDOG_ACTION 1 | |
78 | static unsigned int action = WATCHDOG_ACTION; | |
79 | module_param(action, uint, 0444); | |
80 | MODULE_PARM_DESC(action, "Action taken when watchdog expires (default=" | |
81 | __MODULE_STRING(WATCHDOG_ACTION) ")"); | |
82 | ||
83 | static bool nowayout = WATCHDOG_NOWAYOUT; | |
84 | module_param(nowayout, bool, 0444); | |
85 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | |
86 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
87 | ||
88 | #define WATCHDOG_TIMEOUT 60 | |
89 | static unsigned int timeout = WATCHDOG_TIMEOUT; | |
90 | module_param(timeout, uint, 0444); | |
91 | MODULE_PARM_DESC(timeout, "Initial watchdog timeout in seconds (default=" | |
92 | __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | |
93 | ||
94 | struct pseries_wdt { | |
95 | struct watchdog_device wd; | |
96 | unsigned long action; | |
97 | unsigned long num; /* Watchdog numbers are 1-based */ | |
98 | }; | |
99 | ||
100 | static int pseries_wdt_start(struct watchdog_device *wdd) | |
101 | { | |
102 | struct pseries_wdt *pw = watchdog_get_drvdata(wdd); | |
103 | struct device *dev = wdd->parent; | |
104 | unsigned long flags, msecs; | |
105 | long rc; | |
106 | ||
107 | flags = pw->action | PSERIES_WDTF_OP_START; | |
108 | msecs = wdd->timeout * MSEC_PER_SEC; | |
109 | rc = plpar_hcall_norets(H_WATCHDOG, flags, pw->num, msecs); | |
110 | if (rc != H_SUCCESS) { | |
111 | dev_crit(dev, "H_WATCHDOG: %ld: failed to start timer %lu", | |
112 | rc, pw->num); | |
113 | return -EIO; | |
114 | } | |
115 | return 0; | |
116 | } | |
117 | ||
118 | static int pseries_wdt_stop(struct watchdog_device *wdd) | |
119 | { | |
120 | struct pseries_wdt *pw = watchdog_get_drvdata(wdd); | |
121 | struct device *dev = wdd->parent; | |
122 | long rc; | |
123 | ||
124 | rc = plpar_hcall_norets(H_WATCHDOG, PSERIES_WDTF_OP_STOP, pw->num); | |
125 | if (rc != H_SUCCESS && rc != H_NOOP) { | |
126 | dev_crit(dev, "H_WATCHDOG: %ld: failed to stop timer %lu", | |
127 | rc, pw->num); | |
128 | return -EIO; | |
129 | } | |
130 | return 0; | |
131 | } | |
132 | ||
133 | static struct watchdog_info pseries_wdt_info = { | |
134 | .identity = DRV_NAME, | |
135 | .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | |
136 | | WDIOF_PRETIMEOUT, | |
137 | }; | |
138 | ||
139 | static const struct watchdog_ops pseries_wdt_ops = { | |
140 | .owner = THIS_MODULE, | |
141 | .start = pseries_wdt_start, | |
142 | .stop = pseries_wdt_stop, | |
143 | }; | |
144 | ||
145 | static int pseries_wdt_probe(struct platform_device *pdev) | |
146 | { | |
147 | unsigned long ret[PLPAR_HCALL_BUFSIZE] = { 0 }; | |
148 | struct pseries_wdt *pw; | |
149 | unsigned long cap; | |
150 | long msecs, rc; | |
151 | int err; | |
152 | ||
153 | rc = plpar_hcall(H_WATCHDOG, ret, PSERIES_WDTF_OP_QUERY); | |
154 | if (rc == H_FUNCTION) | |
155 | return -ENODEV; | |
156 | if (rc != H_SUCCESS) | |
157 | return -EIO; | |
158 | cap = ret[0]; | |
159 | ||
160 | pw = devm_kzalloc(&pdev->dev, sizeof(*pw), GFP_KERNEL); | |
161 | if (!pw) | |
162 | return -ENOMEM; | |
163 | ||
164 | /* | |
165 | * Assume watchdogNumber 1 for now. If we ever support | |
166 | * multiple timers we will need to devise a way to choose a | |
167 | * distinct watchdogNumber for each platform device at device | |
168 | * registration time. | |
169 | */ | |
170 | pw->num = 1; | |
171 | if (PSERIES_WDTQ_MAX_NUMBER(cap) < pw->num) | |
172 | return -ENODEV; | |
173 | ||
174 | if (action >= ARRAY_SIZE(pseries_wdt_action)) | |
175 | return -EINVAL; | |
176 | pw->action = pseries_wdt_action[action]; | |
177 | ||
178 | pw->wd.parent = &pdev->dev; | |
179 | pw->wd.info = &pseries_wdt_info; | |
180 | pw->wd.ops = &pseries_wdt_ops; | |
181 | msecs = PSERIES_WDTQ_MIN_TIMEOUT(cap); | |
182 | pw->wd.min_timeout = DIV_ROUND_UP(msecs, MSEC_PER_SEC); | |
183 | pw->wd.max_timeout = UINT_MAX / 1000; /* from linux/watchdog.h */ | |
184 | pw->wd.timeout = timeout; | |
185 | if (watchdog_init_timeout(&pw->wd, 0, NULL)) | |
186 | return -EINVAL; | |
187 | watchdog_set_nowayout(&pw->wd, nowayout); | |
188 | watchdog_stop_on_reboot(&pw->wd); | |
189 | watchdog_stop_on_unregister(&pw->wd); | |
190 | watchdog_set_drvdata(&pw->wd, pw); | |
191 | ||
192 | err = devm_watchdog_register_device(&pdev->dev, &pw->wd); | |
193 | if (err) | |
194 | return err; | |
195 | ||
196 | platform_set_drvdata(pdev, &pw->wd); | |
197 | ||
198 | return 0; | |
199 | } | |
200 | ||
201 | static int pseries_wdt_suspend(struct platform_device *pdev, pm_message_t state) | |
202 | { | |
203 | struct watchdog_device *wd = platform_get_drvdata(pdev); | |
204 | ||
205 | if (watchdog_active(wd)) | |
206 | return pseries_wdt_stop(wd); | |
207 | return 0; | |
208 | } | |
209 | ||
210 | static int pseries_wdt_resume(struct platform_device *pdev) | |
211 | { | |
212 | struct watchdog_device *wd = platform_get_drvdata(pdev); | |
213 | ||
214 | if (watchdog_active(wd)) | |
215 | return pseries_wdt_start(wd); | |
216 | return 0; | |
217 | } | |
218 | ||
219 | static const struct platform_device_id pseries_wdt_id[] = { | |
220 | { .name = "pseries-wdt" }, | |
221 | {} | |
222 | }; | |
223 | MODULE_DEVICE_TABLE(platform, pseries_wdt_id); | |
224 | ||
225 | static struct platform_driver pseries_wdt_driver = { | |
226 | .driver = { | |
227 | .name = DRV_NAME, | |
228 | }, | |
229 | .id_table = pseries_wdt_id, | |
230 | .probe = pseries_wdt_probe, | |
231 | .resume = pseries_wdt_resume, | |
232 | .suspend = pseries_wdt_suspend, | |
233 | }; | |
234 | module_platform_driver(pseries_wdt_driver); | |
235 | ||
236 | MODULE_AUTHOR("Alexey Kardashevskiy"); | |
237 | MODULE_AUTHOR("Scott Cheloha"); | |
238 | MODULE_DESCRIPTION("POWER Architecture Platform Watchdog Driver"); | |
239 | MODULE_LICENSE("GPL"); |