1 | // SPDX-License-Identifier: GPL-1.0+ |
2 | /* |
3 | * IBM Automatic Server Restart driver. |
4 | * |
5 | * Copyright (c) 2005 Andrey Panin <pazke@donpac.ru> |
6 | * |
7 | * Based on driver written by Pete Reynolds. |
8 | * Copyright (c) IBM Corporation, 1998-2004. |
9 | * |
10 | */ |
11 | |
12 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
13 | |
14 | #include <linux/fs.h> |
15 | #include <linux/kernel.h> |
16 | #include <linux/module.h> |
17 | #include <linux/pci.h> |
18 | #include <linux/timer.h> |
19 | #include <linux/miscdevice.h> |
20 | #include <linux/watchdog.h> |
21 | #include <linux/dmi.h> |
22 | #include <linux/io.h> |
23 | #include <linux/uaccess.h> |
24 | |
25 | |
26 | enum { |
27 | ASMTYPE_UNKNOWN, |
28 | ASMTYPE_TOPAZ, |
29 | ASMTYPE_JASPER, |
30 | ASMTYPE_PEARL, |
31 | ASMTYPE_JUNIPER, |
32 | ASMTYPE_SPRUCE, |
33 | }; |
34 | |
35 | #define TOPAZ_ASR_REG_OFFSET 4 |
36 | #define TOPAZ_ASR_TOGGLE 0x40 |
37 | #define TOPAZ_ASR_DISABLE 0x80 |
38 | |
39 | /* PEARL ASR S/W REGISTER SUPERIO PORT ADDRESSES */ |
40 | #define PEARL_BASE 0xe04 |
41 | #define PEARL_WRITE 0xe06 |
42 | #define PEARL_READ 0xe07 |
43 | |
44 | #define PEARL_ASR_DISABLE_MASK 0x80 /* bit 7: disable = 1, enable = 0 */ |
45 | #define PEARL_ASR_TOGGLE_MASK 0x40 /* bit 6: 0, then 1, then 0 */ |
46 | |
47 | /* JASPER OFFSET FROM SIO BASE ADDR TO ASR S/W REGISTERS. */ |
48 | #define JASPER_ASR_REG_OFFSET 0x38 |
49 | |
50 | #define JASPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1, enable = 0 */ |
51 | #define JASPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */ |
52 | |
53 | #define JUNIPER_BASE_ADDRESS 0x54b /* Base address of Juniper ASR */ |
54 | #define JUNIPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1 enable = 0 */ |
55 | #define JUNIPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */ |
56 | |
57 | #define SPRUCE_BASE_ADDRESS 0x118e /* Base address of Spruce ASR */ |
58 | #define SPRUCE_ASR_DISABLE_MASK 0x01 /* bit 1: disable = 1 enable = 0 */ |
59 | #define SPRUCE_ASR_TOGGLE_MASK 0x02 /* bit 0: 0, then 1, then 0 */ |
60 | |
61 | |
62 | static bool nowayout = WATCHDOG_NOWAYOUT; |
63 | |
64 | static unsigned long asr_is_open; |
65 | static char asr_expect_close; |
66 | |
67 | static unsigned int asr_type, asr_base, asr_length; |
68 | static unsigned int asr_read_addr, asr_write_addr; |
69 | static unsigned char asr_toggle_mask, asr_disable_mask; |
70 | static DEFINE_SPINLOCK(asr_lock); |
71 | |
72 | static void __asr_toggle(void) |
73 | { |
74 | unsigned char reg; |
75 | |
76 | reg = inb(port: asr_read_addr); |
77 | |
78 | outb(value: reg & ~asr_toggle_mask, port: asr_write_addr); |
79 | reg = inb(port: asr_read_addr); |
80 | |
81 | outb(value: reg | asr_toggle_mask, port: asr_write_addr); |
82 | reg = inb(port: asr_read_addr); |
83 | |
84 | outb(value: reg & ~asr_toggle_mask, port: asr_write_addr); |
85 | reg = inb(port: asr_read_addr); |
86 | } |
87 | |
88 | static void asr_toggle(void) |
89 | { |
90 | spin_lock(lock: &asr_lock); |
91 | __asr_toggle(); |
92 | spin_unlock(lock: &asr_lock); |
93 | } |
94 | |
95 | static void asr_enable(void) |
96 | { |
97 | unsigned char reg; |
98 | |
99 | spin_lock(lock: &asr_lock); |
100 | if (asr_type == ASMTYPE_TOPAZ) { |
101 | /* asr_write_addr == asr_read_addr */ |
102 | reg = inb(port: asr_read_addr); |
103 | outb(value: reg & ~(TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE), |
104 | port: asr_read_addr); |
105 | } else { |
106 | /* |
107 | * First make sure the hardware timer is reset by toggling |
108 | * ASR hardware timer line. |
109 | */ |
110 | __asr_toggle(); |
111 | |
112 | reg = inb(port: asr_read_addr); |
113 | outb(value: reg & ~asr_disable_mask, port: asr_write_addr); |
114 | } |
115 | reg = inb(port: asr_read_addr); |
116 | spin_unlock(lock: &asr_lock); |
117 | } |
118 | |
119 | static void asr_disable(void) |
120 | { |
121 | unsigned char reg; |
122 | |
123 | spin_lock(lock: &asr_lock); |
124 | reg = inb(port: asr_read_addr); |
125 | |
126 | if (asr_type == ASMTYPE_TOPAZ) |
127 | /* asr_write_addr == asr_read_addr */ |
128 | outb(value: reg | TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE, |
129 | port: asr_read_addr); |
130 | else { |
131 | outb(value: reg | asr_toggle_mask, port: asr_write_addr); |
132 | reg = inb(port: asr_read_addr); |
133 | |
134 | outb(value: reg | asr_disable_mask, port: asr_write_addr); |
135 | } |
136 | reg = inb(port: asr_read_addr); |
137 | spin_unlock(lock: &asr_lock); |
138 | } |
139 | |
140 | static int __init asr_get_base_address(void) |
141 | { |
142 | unsigned char low, high; |
143 | const char *type = "" ; |
144 | |
145 | asr_length = 1; |
146 | |
147 | switch (asr_type) { |
148 | case ASMTYPE_TOPAZ: |
149 | /* SELECT SuperIO CHIP FOR QUERYING |
150 | (WRITE 0x07 TO BOTH 0x2E and 0x2F) */ |
151 | outb(value: 0x07, port: 0x2e); |
152 | outb(value: 0x07, port: 0x2f); |
153 | |
154 | /* SELECT AND READ THE HIGH-NIBBLE OF THE GPIO BASE ADDRESS */ |
155 | outb(value: 0x60, port: 0x2e); |
156 | high = inb(port: 0x2f); |
157 | |
158 | /* SELECT AND READ THE LOW-NIBBLE OF THE GPIO BASE ADDRESS */ |
159 | outb(value: 0x61, port: 0x2e); |
160 | low = inb(port: 0x2f); |
161 | |
162 | asr_base = (high << 16) | low; |
163 | asr_read_addr = asr_write_addr = |
164 | asr_base + TOPAZ_ASR_REG_OFFSET; |
165 | asr_length = 5; |
166 | |
167 | break; |
168 | |
169 | case ASMTYPE_JASPER: |
170 | type = "Jaspers " ; |
171 | #if 0 |
172 | u32 r; |
173 | /* Suggested fix */ |
174 | pdev = pci_get_bus_and_slot(0, DEVFN(0x1f, 0)); |
175 | if (pdev == NULL) |
176 | return -ENODEV; |
177 | pci_read_config_dword(pdev, 0x58, &r); |
178 | asr_base = r & 0xFFFE; |
179 | pci_dev_put(pdev); |
180 | #else |
181 | /* FIXME: need to use pci_config_lock here, |
182 | but it's not exported */ |
183 | |
184 | /* spin_lock_irqsave(&pci_config_lock, flags);*/ |
185 | |
186 | /* Select the SuperIO chip in the PCI I/O port register */ |
187 | outl(value: 0x8000f858, port: 0xcf8); |
188 | |
189 | /* BUS 0, Slot 1F, fnc 0, offset 58 */ |
190 | |
191 | /* |
192 | * Read the base address for the SuperIO chip. |
193 | * Only the lower 16 bits are valid, but the address is word |
194 | * aligned so the last bit must be masked off. |
195 | */ |
196 | asr_base = inl(port: 0xcfc) & 0xfffe; |
197 | |
198 | /* spin_unlock_irqrestore(&pci_config_lock, flags);*/ |
199 | #endif |
200 | asr_read_addr = asr_write_addr = |
201 | asr_base + JASPER_ASR_REG_OFFSET; |
202 | asr_toggle_mask = JASPER_ASR_TOGGLE_MASK; |
203 | asr_disable_mask = JASPER_ASR_DISABLE_MASK; |
204 | asr_length = JASPER_ASR_REG_OFFSET + 1; |
205 | |
206 | break; |
207 | |
208 | case ASMTYPE_PEARL: |
209 | type = "Pearls " ; |
210 | asr_base = PEARL_BASE; |
211 | asr_read_addr = PEARL_READ; |
212 | asr_write_addr = PEARL_WRITE; |
213 | asr_toggle_mask = PEARL_ASR_TOGGLE_MASK; |
214 | asr_disable_mask = PEARL_ASR_DISABLE_MASK; |
215 | asr_length = 4; |
216 | break; |
217 | |
218 | case ASMTYPE_JUNIPER: |
219 | type = "Junipers " ; |
220 | asr_base = JUNIPER_BASE_ADDRESS; |
221 | asr_read_addr = asr_write_addr = asr_base; |
222 | asr_toggle_mask = JUNIPER_ASR_TOGGLE_MASK; |
223 | asr_disable_mask = JUNIPER_ASR_DISABLE_MASK; |
224 | break; |
225 | |
226 | case ASMTYPE_SPRUCE: |
227 | type = "Spruce's " ; |
228 | asr_base = SPRUCE_BASE_ADDRESS; |
229 | asr_read_addr = asr_write_addr = asr_base; |
230 | asr_toggle_mask = SPRUCE_ASR_TOGGLE_MASK; |
231 | asr_disable_mask = SPRUCE_ASR_DISABLE_MASK; |
232 | break; |
233 | } |
234 | |
235 | if (!request_region(asr_base, asr_length, "ibmasr" )) { |
236 | pr_err("address %#x already in use\n" , asr_base); |
237 | return -EBUSY; |
238 | } |
239 | |
240 | pr_info("found %sASR @ addr %#x\n" , type, asr_base); |
241 | |
242 | return 0; |
243 | } |
244 | |
245 | |
246 | static ssize_t asr_write(struct file *file, const char __user *buf, |
247 | size_t count, loff_t *ppos) |
248 | { |
249 | if (count) { |
250 | if (!nowayout) { |
251 | size_t i; |
252 | |
253 | /* In case it was set long ago */ |
254 | asr_expect_close = 0; |
255 | |
256 | for (i = 0; i != count; i++) { |
257 | char c; |
258 | if (get_user(c, buf + i)) |
259 | return -EFAULT; |
260 | if (c == 'V') |
261 | asr_expect_close = 42; |
262 | } |
263 | } |
264 | asr_toggle(); |
265 | } |
266 | return count; |
267 | } |
268 | |
269 | static long asr_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
270 | { |
271 | static const struct watchdog_info ident = { |
272 | .options = WDIOF_KEEPALIVEPING | |
273 | WDIOF_MAGICCLOSE, |
274 | .identity = "IBM ASR" , |
275 | }; |
276 | void __user *argp = (void __user *)arg; |
277 | int __user *p = argp; |
278 | int heartbeat; |
279 | |
280 | switch (cmd) { |
281 | case WDIOC_GETSUPPORT: |
282 | return copy_to_user(to: argp, from: &ident, n: sizeof(ident)) ? -EFAULT : 0; |
283 | case WDIOC_GETSTATUS: |
284 | case WDIOC_GETBOOTSTATUS: |
285 | return put_user(0, p); |
286 | case WDIOC_SETOPTIONS: |
287 | { |
288 | int new_options, retval = -EINVAL; |
289 | if (get_user(new_options, p)) |
290 | return -EFAULT; |
291 | if (new_options & WDIOS_DISABLECARD) { |
292 | asr_disable(); |
293 | retval = 0; |
294 | } |
295 | if (new_options & WDIOS_ENABLECARD) { |
296 | asr_enable(); |
297 | asr_toggle(); |
298 | retval = 0; |
299 | } |
300 | return retval; |
301 | } |
302 | case WDIOC_KEEPALIVE: |
303 | asr_toggle(); |
304 | return 0; |
305 | /* |
306 | * The hardware has a fixed timeout value, so no WDIOC_SETTIMEOUT |
307 | * and WDIOC_GETTIMEOUT always returns 256. |
308 | */ |
309 | case WDIOC_GETTIMEOUT: |
310 | heartbeat = 256; |
311 | return put_user(heartbeat, p); |
312 | default: |
313 | return -ENOTTY; |
314 | } |
315 | } |
316 | |
317 | static int asr_open(struct inode *inode, struct file *file) |
318 | { |
319 | if (test_and_set_bit(nr: 0, addr: &asr_is_open)) |
320 | return -EBUSY; |
321 | |
322 | asr_toggle(); |
323 | asr_enable(); |
324 | |
325 | return stream_open(inode, filp: file); |
326 | } |
327 | |
328 | static int asr_release(struct inode *inode, struct file *file) |
329 | { |
330 | if (asr_expect_close == 42) |
331 | asr_disable(); |
332 | else { |
333 | pr_crit("unexpected close, not stopping watchdog!\n" ); |
334 | asr_toggle(); |
335 | } |
336 | clear_bit(nr: 0, addr: &asr_is_open); |
337 | asr_expect_close = 0; |
338 | return 0; |
339 | } |
340 | |
341 | static const struct file_operations asr_fops = { |
342 | .owner = THIS_MODULE, |
343 | .llseek = no_llseek, |
344 | .write = asr_write, |
345 | .unlocked_ioctl = asr_ioctl, |
346 | .compat_ioctl = compat_ptr_ioctl, |
347 | .open = asr_open, |
348 | .release = asr_release, |
349 | }; |
350 | |
351 | static struct miscdevice asr_miscdev = { |
352 | .minor = WATCHDOG_MINOR, |
353 | .name = "watchdog" , |
354 | .fops = &asr_fops, |
355 | }; |
356 | |
357 | |
358 | struct ibmasr_id { |
359 | const char *desc; |
360 | int type; |
361 | }; |
362 | |
363 | static struct ibmasr_id ibmasr_id_table[] __initdata = { |
364 | { "IBM Automatic Server Restart - eserver xSeries 220" , ASMTYPE_TOPAZ }, |
365 | { "IBM Automatic Server Restart - Machine Type 8673" , ASMTYPE_PEARL }, |
366 | { "IBM Automatic Server Restart - Machine Type 8480" , ASMTYPE_JASPER }, |
367 | { "IBM Automatic Server Restart - Machine Type 8482" , ASMTYPE_JUNIPER }, |
368 | { "IBM Automatic Server Restart - Machine Type 8648" , ASMTYPE_SPRUCE }, |
369 | { NULL } |
370 | }; |
371 | |
372 | static int __init ibmasr_init(void) |
373 | { |
374 | struct ibmasr_id *id; |
375 | int rc; |
376 | |
377 | for (id = ibmasr_id_table; id->desc; id++) { |
378 | if (dmi_find_device(type: DMI_DEV_TYPE_OTHER, name: id->desc, NULL)) { |
379 | asr_type = id->type; |
380 | break; |
381 | } |
382 | } |
383 | |
384 | if (!asr_type) |
385 | return -ENODEV; |
386 | |
387 | rc = asr_get_base_address(); |
388 | if (rc) |
389 | return rc; |
390 | |
391 | rc = misc_register(misc: &asr_miscdev); |
392 | if (rc < 0) { |
393 | release_region(asr_base, asr_length); |
394 | pr_err("failed to register misc device\n" ); |
395 | return rc; |
396 | } |
397 | |
398 | return 0; |
399 | } |
400 | |
401 | static void __exit ibmasr_exit(void) |
402 | { |
403 | if (!nowayout) |
404 | asr_disable(); |
405 | |
406 | misc_deregister(misc: &asr_miscdev); |
407 | |
408 | release_region(asr_base, asr_length); |
409 | } |
410 | |
411 | module_init(ibmasr_init); |
412 | module_exit(ibmasr_exit); |
413 | |
414 | module_param(nowayout, bool, 0); |
415 | MODULE_PARM_DESC(nowayout, |
416 | "Watchdog cannot be stopped once started (default=" |
417 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")" ); |
418 | |
419 | MODULE_DESCRIPTION("IBM Automatic Server Restart driver" ); |
420 | MODULE_AUTHOR("Andrey Panin" ); |
421 | MODULE_LICENSE("GPL" ); |
422 | |