Commit | Line | Data |
---|---|---|
058dfc76 MW |
1 | /* |
2 | * ACPI watchdog table parsing support. | |
3 | * | |
4 | * Copyright (C) 2016, Intel Corporation | |
5 | * Author: Mika Westerberg <mika.westerberg@linux.intel.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | */ | |
11 | ||
12 | #define pr_fmt(fmt) "ACPI: watchdog: " fmt | |
13 | ||
14 | #include <linux/acpi.h> | |
15 | #include <linux/ioport.h> | |
16 | #include <linux/platform_device.h> | |
17 | ||
18 | #include "internal.h" | |
19 | ||
20 | /** | |
21 | * Returns true if this system should prefer ACPI based watchdog instead of | |
22 | * the native one (which are typically the same hardware). | |
23 | */ | |
24 | bool acpi_has_watchdog(void) | |
25 | { | |
26 | struct acpi_table_header hdr; | |
27 | ||
28 | if (acpi_disabled) | |
29 | return false; | |
30 | ||
31 | return ACPI_SUCCESS(acpi_get_table_header(ACPI_SIG_WDAT, 0, &hdr)); | |
32 | } | |
33 | EXPORT_SYMBOL_GPL(acpi_has_watchdog); | |
34 | ||
35 | void __init acpi_watchdog_init(void) | |
36 | { | |
37 | const struct acpi_wdat_entry *entries; | |
38 | const struct acpi_table_wdat *wdat; | |
39 | struct list_head resource_list; | |
40 | struct resource_entry *rentry; | |
41 | struct platform_device *pdev; | |
42 | struct resource *resources; | |
43 | size_t nresources = 0; | |
44 | acpi_status status; | |
45 | int i; | |
46 | ||
47 | status = acpi_get_table(ACPI_SIG_WDAT, 0, | |
48 | (struct acpi_table_header **)&wdat); | |
49 | if (ACPI_FAILURE(status)) { | |
50 | /* It is fine if there is no WDAT */ | |
51 | return; | |
52 | } | |
53 | ||
54 | /* Watchdog disabled by BIOS */ | |
55 | if (!(wdat->flags & ACPI_WDAT_ENABLED)) | |
56 | return; | |
57 | ||
58 | /* Skip legacy PCI WDT devices */ | |
59 | if (wdat->pci_segment != 0xff || wdat->pci_bus != 0xff || | |
60 | wdat->pci_device != 0xff || wdat->pci_function != 0xff) | |
61 | return; | |
62 | ||
63 | INIT_LIST_HEAD(&resource_list); | |
64 | ||
65 | entries = (struct acpi_wdat_entry *)(wdat + 1); | |
66 | for (i = 0; i < wdat->entries; i++) { | |
67 | const struct acpi_generic_address *gas; | |
68 | struct resource_entry *rentry; | |
69 | struct resource res; | |
70 | bool found; | |
71 | ||
72 | gas = &entries[i].register_region; | |
73 | ||
74 | res.start = gas->address; | |
75 | if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { | |
76 | res.flags = IORESOURCE_MEM; | |
77 | res.end = res.start + ALIGN(gas->access_width, 4); | |
78 | } else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { | |
79 | res.flags = IORESOURCE_IO; | |
80 | res.end = res.start + gas->access_width; | |
81 | } else { | |
82 | pr_warn("Unsupported address space: %u\n", | |
83 | gas->space_id); | |
84 | goto fail_free_resource_list; | |
85 | } | |
86 | ||
87 | found = false; | |
88 | resource_list_for_each_entry(rentry, &resource_list) { | |
89 | if (resource_contains(rentry->res, &res)) { | |
90 | found = true; | |
91 | break; | |
92 | } | |
93 | } | |
94 | ||
95 | if (!found) { | |
96 | rentry = resource_list_create_entry(NULL, 0); | |
97 | if (!rentry) | |
98 | goto fail_free_resource_list; | |
99 | ||
100 | *rentry->res = res; | |
101 | resource_list_add_tail(rentry, &resource_list); | |
102 | nresources++; | |
103 | } | |
104 | } | |
105 | ||
106 | resources = kcalloc(nresources, sizeof(*resources), GFP_KERNEL); | |
107 | if (!resources) | |
108 | goto fail_free_resource_list; | |
109 | ||
110 | i = 0; | |
111 | resource_list_for_each_entry(rentry, &resource_list) | |
112 | resources[i++] = *rentry->res; | |
113 | ||
114 | pdev = platform_device_register_simple("wdat_wdt", PLATFORM_DEVID_NONE, | |
115 | resources, nresources); | |
116 | if (IS_ERR(pdev)) | |
117 | pr_err("Failed to create platform device\n"); | |
118 | ||
119 | kfree(resources); | |
120 | ||
121 | fail_free_resource_list: | |
122 | resource_list_free(&resource_list); | |
123 | } |