Commit | Line | Data |
---|---|---|
6bc75619 DW |
1 | /* |
2 | * Copyright(c) 2013-2015 Intel Corporation. All rights reserved. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of version 2 of the GNU General Public License as | |
6 | * published by the Free Software Foundation. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, but | |
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
11 | * General Public License for more details. | |
12 | */ | |
b28f08ce | 13 | #include <linux/memremap.h> |
6bc75619 DW |
14 | #include <linux/rculist.h> |
15 | #include <linux/export.h> | |
16 | #include <linux/ioport.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/types.h> | |
b28f08ce | 19 | #include <linux/pfn_t.h> |
c14a868a | 20 | #include <linux/acpi.h> |
6bc75619 | 21 | #include <linux/io.h> |
9bfa8496 | 22 | #include <linux/mm.h> |
6bc75619 DW |
23 | #include "nfit_test.h" |
24 | ||
25 | static LIST_HEAD(iomap_head); | |
26 | ||
27 | static struct iomap_ops { | |
28 | nfit_test_lookup_fn nfit_test_lookup; | |
a7de92da | 29 | nfit_test_evaluate_dsm_fn evaluate_dsm; |
6bc75619 DW |
30 | struct list_head list; |
31 | } iomap_ops = { | |
32 | .list = LIST_HEAD_INIT(iomap_ops.list), | |
33 | }; | |
34 | ||
a7de92da DW |
35 | void nfit_test_setup(nfit_test_lookup_fn lookup, |
36 | nfit_test_evaluate_dsm_fn evaluate) | |
6bc75619 DW |
37 | { |
38 | iomap_ops.nfit_test_lookup = lookup; | |
a7de92da | 39 | iomap_ops.evaluate_dsm = evaluate; |
6bc75619 DW |
40 | list_add_rcu(&iomap_ops.list, &iomap_head); |
41 | } | |
42 | EXPORT_SYMBOL(nfit_test_setup); | |
43 | ||
44 | void nfit_test_teardown(void) | |
45 | { | |
46 | list_del_rcu(&iomap_ops.list); | |
47 | synchronize_rcu(); | |
48 | } | |
49 | EXPORT_SYMBOL(nfit_test_teardown); | |
50 | ||
9bfa8496 | 51 | static struct nfit_test_resource *__get_nfit_res(resource_size_t resource) |
6bc75619 DW |
52 | { |
53 | struct iomap_ops *ops; | |
54 | ||
55 | ops = list_first_or_null_rcu(&iomap_head, typeof(*ops), list); | |
56 | if (ops) | |
57 | return ops->nfit_test_lookup(resource); | |
58 | return NULL; | |
59 | } | |
60 | ||
f295e53b | 61 | struct nfit_test_resource *get_nfit_res(resource_size_t resource) |
6bc75619 | 62 | { |
9bfa8496 | 63 | struct nfit_test_resource *res; |
6bc75619 DW |
64 | |
65 | rcu_read_lock(); | |
9bfa8496 | 66 | res = __get_nfit_res(resource); |
6bc75619 | 67 | rcu_read_unlock(); |
9bfa8496 DW |
68 | |
69 | return res; | |
70 | } | |
f295e53b | 71 | EXPORT_SYMBOL(get_nfit_res); |
9bfa8496 DW |
72 | |
73 | void __iomem *__nfit_test_ioremap(resource_size_t offset, unsigned long size, | |
74 | void __iomem *(*fallback_fn)(resource_size_t, unsigned long)) | |
75 | { | |
76 | struct nfit_test_resource *nfit_res = get_nfit_res(offset); | |
77 | ||
6bc75619 DW |
78 | if (nfit_res) |
79 | return (void __iomem *) nfit_res->buf + offset | |
bd4cd745 | 80 | - nfit_res->res.start; |
6bc75619 DW |
81 | return fallback_fn(offset, size); |
82 | } | |
83 | ||
9d27a87e DW |
84 | void __iomem *__wrap_devm_ioremap_nocache(struct device *dev, |
85 | resource_size_t offset, unsigned long size) | |
86 | { | |
9bfa8496 | 87 | struct nfit_test_resource *nfit_res = get_nfit_res(offset); |
9d27a87e | 88 | |
9d27a87e DW |
89 | if (nfit_res) |
90 | return (void __iomem *) nfit_res->buf + offset | |
bd4cd745 | 91 | - nfit_res->res.start; |
9d27a87e DW |
92 | return devm_ioremap_nocache(dev, offset, size); |
93 | } | |
94 | EXPORT_SYMBOL(__wrap_devm_ioremap_nocache); | |
95 | ||
708ab62b CH |
96 | void *__wrap_devm_memremap(struct device *dev, resource_size_t offset, |
97 | size_t size, unsigned long flags) | |
6bc75619 | 98 | { |
9bfa8496 | 99 | struct nfit_test_resource *nfit_res = get_nfit_res(offset); |
e836a256 | 100 | |
e836a256 | 101 | if (nfit_res) |
bd4cd745 | 102 | return nfit_res->buf + offset - nfit_res->res.start; |
708ab62b | 103 | return devm_memremap(dev, offset, size, flags); |
6bc75619 | 104 | } |
708ab62b | 105 | EXPORT_SYMBOL(__wrap_devm_memremap); |
6bc75619 | 106 | |
979fccfb DW |
107 | void *__wrap_devm_memremap_pages(struct device *dev, struct resource *res, |
108 | struct percpu_ref *ref, struct vmem_altmap *altmap) | |
109 | { | |
110 | resource_size_t offset = res->start; | |
9bfa8496 | 111 | struct nfit_test_resource *nfit_res = get_nfit_res(offset); |
979fccfb | 112 | |
979fccfb | 113 | if (nfit_res) |
bd4cd745 | 114 | return nfit_res->buf + offset - nfit_res->res.start; |
979fccfb DW |
115 | return devm_memremap_pages(dev, res, ref, altmap); |
116 | } | |
117 | EXPORT_SYMBOL(__wrap_devm_memremap_pages); | |
118 | ||
76e9f0ee | 119 | pfn_t __wrap_phys_to_pfn_t(phys_addr_t addr, unsigned long flags) |
979fccfb | 120 | { |
9bfa8496 | 121 | struct nfit_test_resource *nfit_res = get_nfit_res(addr); |
979fccfb | 122 | |
979fccfb DW |
123 | if (nfit_res) |
124 | flags &= ~PFN_MAP; | |
125 | return phys_to_pfn_t(addr, flags); | |
126 | } | |
127 | EXPORT_SYMBOL(__wrap_phys_to_pfn_t); | |
979fccfb | 128 | |
67a3e8fe RZ |
129 | void *__wrap_memremap(resource_size_t offset, size_t size, |
130 | unsigned long flags) | |
131 | { | |
9bfa8496 | 132 | struct nfit_test_resource *nfit_res = get_nfit_res(offset); |
67a3e8fe | 133 | |
67a3e8fe | 134 | if (nfit_res) |
bd4cd745 | 135 | return nfit_res->buf + offset - nfit_res->res.start; |
67a3e8fe RZ |
136 | return memremap(offset, size, flags); |
137 | } | |
138 | EXPORT_SYMBOL(__wrap_memremap); | |
139 | ||
32ab0a3f DW |
140 | void __wrap_devm_memunmap(struct device *dev, void *addr) |
141 | { | |
9bfa8496 | 142 | struct nfit_test_resource *nfit_res = get_nfit_res((long) addr); |
32ab0a3f | 143 | |
32ab0a3f DW |
144 | if (nfit_res) |
145 | return; | |
146 | return devm_memunmap(dev, addr); | |
147 | } | |
148 | EXPORT_SYMBOL(__wrap_devm_memunmap); | |
149 | ||
6bc75619 DW |
150 | void __iomem *__wrap_ioremap_nocache(resource_size_t offset, unsigned long size) |
151 | { | |
152 | return __nfit_test_ioremap(offset, size, ioremap_nocache); | |
153 | } | |
154 | EXPORT_SYMBOL(__wrap_ioremap_nocache); | |
155 | ||
9d27a87e DW |
156 | void __iomem *__wrap_ioremap_wc(resource_size_t offset, unsigned long size) |
157 | { | |
158 | return __nfit_test_ioremap(offset, size, ioremap_wc); | |
159 | } | |
160 | EXPORT_SYMBOL(__wrap_ioremap_wc); | |
161 | ||
6bc75619 DW |
162 | void __wrap_iounmap(volatile void __iomem *addr) |
163 | { | |
9bfa8496 | 164 | struct nfit_test_resource *nfit_res = get_nfit_res((long) addr); |
6bc75619 DW |
165 | if (nfit_res) |
166 | return; | |
167 | return iounmap(addr); | |
168 | } | |
169 | EXPORT_SYMBOL(__wrap_iounmap); | |
170 | ||
67a3e8fe RZ |
171 | void __wrap_memunmap(void *addr) |
172 | { | |
9bfa8496 | 173 | struct nfit_test_resource *nfit_res = get_nfit_res((long) addr); |
67a3e8fe | 174 | |
67a3e8fe RZ |
175 | if (nfit_res) |
176 | return; | |
177 | return memunmap(addr); | |
178 | } | |
179 | EXPORT_SYMBOL(__wrap_memunmap); | |
180 | ||
bd4cd745 DW |
181 | static bool nfit_test_release_region(struct device *dev, |
182 | struct resource *parent, resource_size_t start, | |
183 | resource_size_t n); | |
184 | ||
185 | static void nfit_devres_release(struct device *dev, void *data) | |
186 | { | |
187 | struct resource *res = *((struct resource **) data); | |
188 | ||
189 | WARN_ON(!nfit_test_release_region(NULL, &iomem_resource, res->start, | |
190 | resource_size(res))); | |
191 | } | |
192 | ||
193 | static int match(struct device *dev, void *__res, void *match_data) | |
194 | { | |
195 | struct resource *res = *((struct resource **) __res); | |
196 | resource_size_t start = *((resource_size_t *) match_data); | |
197 | ||
198 | return res->start == start; | |
199 | } | |
200 | ||
201 | static bool nfit_test_release_region(struct device *dev, | |
202 | struct resource *parent, resource_size_t start, | |
203 | resource_size_t n) | |
204 | { | |
205 | if (parent == &iomem_resource) { | |
206 | struct nfit_test_resource *nfit_res = get_nfit_res(start); | |
207 | ||
208 | if (nfit_res) { | |
209 | struct nfit_test_request *req; | |
210 | struct resource *res = NULL; | |
211 | ||
212 | if (dev) { | |
213 | devres_release(dev, nfit_devres_release, match, | |
214 | &start); | |
215 | return true; | |
216 | } | |
217 | ||
218 | spin_lock(&nfit_res->lock); | |
219 | list_for_each_entry(req, &nfit_res->requests, list) | |
220 | if (req->res.start == start) { | |
221 | res = &req->res; | |
222 | list_del(&req->list); | |
223 | break; | |
224 | } | |
225 | spin_unlock(&nfit_res->lock); | |
226 | ||
227 | WARN(!res || resource_size(res) != n, | |
228 | "%s: start: %llx n: %llx mismatch: %pr\n", | |
229 | __func__, start, n, res); | |
230 | if (res) | |
231 | kfree(req); | |
232 | return true; | |
233 | } | |
234 | } | |
235 | return false; | |
236 | } | |
237 | ||
708ab62b CH |
238 | static struct resource *nfit_test_request_region(struct device *dev, |
239 | struct resource *parent, resource_size_t start, | |
240 | resource_size_t n, const char *name, int flags) | |
6bc75619 DW |
241 | { |
242 | struct nfit_test_resource *nfit_res; | |
243 | ||
244 | if (parent == &iomem_resource) { | |
6bc75619 | 245 | nfit_res = get_nfit_res(start); |
6bc75619 | 246 | if (nfit_res) { |
bd4cd745 DW |
247 | struct nfit_test_request *req; |
248 | struct resource *res = NULL; | |
6bc75619 | 249 | |
bd4cd745 DW |
250 | if (start + n > nfit_res->res.start |
251 | + resource_size(&nfit_res->res)) { | |
6bc75619 DW |
252 | pr_debug("%s: start: %llx n: %llx overflow: %pr\n", |
253 | __func__, start, n, | |
bd4cd745 DW |
254 | &nfit_res->res); |
255 | return NULL; | |
256 | } | |
257 | ||
258 | spin_lock(&nfit_res->lock); | |
259 | list_for_each_entry(req, &nfit_res->requests, list) | |
260 | if (start == req->res.start) { | |
261 | res = &req->res; | |
262 | break; | |
263 | } | |
264 | spin_unlock(&nfit_res->lock); | |
265 | ||
266 | if (res) { | |
267 | WARN(1, "%pr already busy\n", res); | |
6bc75619 DW |
268 | return NULL; |
269 | } | |
270 | ||
bd4cd745 DW |
271 | req = kzalloc(sizeof(*req), GFP_KERNEL); |
272 | if (!req) | |
273 | return NULL; | |
274 | INIT_LIST_HEAD(&req->list); | |
275 | res = &req->res; | |
276 | ||
6bc75619 DW |
277 | res->start = start; |
278 | res->end = start + n - 1; | |
279 | res->name = name; | |
280 | res->flags = resource_type(parent); | |
281 | res->flags |= IORESOURCE_BUSY | flags; | |
bd4cd745 DW |
282 | spin_lock(&nfit_res->lock); |
283 | list_add(&req->list, &nfit_res->requests); | |
284 | spin_unlock(&nfit_res->lock); | |
285 | ||
286 | if (dev) { | |
287 | struct resource **d; | |
288 | ||
289 | d = devres_alloc(nfit_devres_release, | |
290 | sizeof(struct resource *), | |
291 | GFP_KERNEL); | |
292 | if (!d) | |
293 | return NULL; | |
294 | *d = res; | |
295 | devres_add(dev, d); | |
296 | } | |
297 | ||
6bc75619 DW |
298 | pr_debug("%s: %pr\n", __func__, res); |
299 | return res; | |
300 | } | |
301 | } | |
708ab62b CH |
302 | if (dev) |
303 | return __devm_request_region(dev, parent, start, n, name); | |
6bc75619 DW |
304 | return __request_region(parent, start, n, name, flags); |
305 | } | |
708ab62b CH |
306 | |
307 | struct resource *__wrap___request_region(struct resource *parent, | |
308 | resource_size_t start, resource_size_t n, const char *name, | |
309 | int flags) | |
310 | { | |
311 | return nfit_test_request_region(NULL, parent, start, n, name, flags); | |
312 | } | |
6bc75619 DW |
313 | EXPORT_SYMBOL(__wrap___request_region); |
314 | ||
ee8520fe DW |
315 | int __wrap_insert_resource(struct resource *parent, struct resource *res) |
316 | { | |
317 | if (get_nfit_res(res->start)) | |
318 | return 0; | |
319 | return insert_resource(parent, res); | |
320 | } | |
321 | EXPORT_SYMBOL(__wrap_insert_resource); | |
322 | ||
323 | int __wrap_remove_resource(struct resource *res) | |
324 | { | |
325 | if (get_nfit_res(res->start)) | |
326 | return 0; | |
327 | return remove_resource(res); | |
328 | } | |
329 | EXPORT_SYMBOL(__wrap_remove_resource); | |
330 | ||
708ab62b CH |
331 | struct resource *__wrap___devm_request_region(struct device *dev, |
332 | struct resource *parent, resource_size_t start, | |
333 | resource_size_t n, const char *name) | |
334 | { | |
335 | if (!dev) | |
336 | return NULL; | |
337 | return nfit_test_request_region(dev, parent, start, n, name, 0); | |
338 | } | |
339 | EXPORT_SYMBOL(__wrap___devm_request_region); | |
340 | ||
200c79da DW |
341 | void __wrap___release_region(struct resource *parent, resource_size_t start, |
342 | resource_size_t n) | |
343 | { | |
bd4cd745 | 344 | if (!nfit_test_release_region(NULL, parent, start, n)) |
200c79da | 345 | __release_region(parent, start, n); |
6bc75619 DW |
346 | } |
347 | EXPORT_SYMBOL(__wrap___release_region); | |
348 | ||
200c79da DW |
349 | void __wrap___devm_release_region(struct device *dev, struct resource *parent, |
350 | resource_size_t start, resource_size_t n) | |
351 | { | |
bd4cd745 | 352 | if (!nfit_test_release_region(dev, parent, start, n)) |
200c79da DW |
353 | __devm_release_region(dev, parent, start, n); |
354 | } | |
355 | EXPORT_SYMBOL(__wrap___devm_release_region); | |
356 | ||
c14a868a DW |
357 | acpi_status __wrap_acpi_evaluate_object(acpi_handle handle, acpi_string path, |
358 | struct acpi_object_list *p, struct acpi_buffer *buf) | |
359 | { | |
360 | struct nfit_test_resource *nfit_res = get_nfit_res((long) handle); | |
361 | union acpi_object **obj; | |
362 | ||
363 | if (!nfit_res || strcmp(path, "_FIT") || !buf) | |
364 | return acpi_evaluate_object(handle, path, p, buf); | |
365 | ||
366 | obj = nfit_res->buf; | |
367 | buf->length = sizeof(union acpi_object); | |
368 | buf->pointer = *obj; | |
369 | return AE_OK; | |
370 | } | |
371 | EXPORT_SYMBOL(__wrap_acpi_evaluate_object); | |
372 | ||
94116f81 | 373 | union acpi_object * __wrap_acpi_evaluate_dsm(acpi_handle handle, const guid_t *guid, |
a7de92da DW |
374 | u64 rev, u64 func, union acpi_object *argv4) |
375 | { | |
376 | union acpi_object *obj = ERR_PTR(-ENXIO); | |
377 | struct iomap_ops *ops; | |
378 | ||
379 | rcu_read_lock(); | |
380 | ops = list_first_or_null_rcu(&iomap_head, typeof(*ops), list); | |
381 | if (ops) | |
94116f81 | 382 | obj = ops->evaluate_dsm(handle, guid, rev, func, argv4); |
a7de92da DW |
383 | rcu_read_unlock(); |
384 | ||
385 | if (IS_ERR(obj)) | |
94116f81 | 386 | return acpi_evaluate_dsm(handle, guid, rev, func, argv4); |
a7de92da DW |
387 | return obj; |
388 | } | |
389 | EXPORT_SYMBOL(__wrap_acpi_evaluate_dsm); | |
390 | ||
6bc75619 | 391 | MODULE_LICENSE("GPL v2"); |