]>
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 | |
a95c90f1 DW |
107 | static void nfit_test_kill(void *_pgmap) |
108 | { | |
109 | struct dev_pagemap *pgmap = _pgmap; | |
110 | ||
111 | pgmap->kill(pgmap->ref); | |
112 | } | |
113 | ||
e8d51348 | 114 | void *__wrap_devm_memremap_pages(struct device *dev, struct dev_pagemap *pgmap) |
979fccfb | 115 | { |
e8d51348 | 116 | resource_size_t offset = pgmap->res.start; |
9bfa8496 | 117 | struct nfit_test_resource *nfit_res = get_nfit_res(offset); |
979fccfb | 118 | |
a95c90f1 DW |
119 | if (nfit_res) { |
120 | int rc; | |
121 | ||
122 | rc = devm_add_action_or_reset(dev, nfit_test_kill, pgmap); | |
123 | if (rc) | |
124 | return ERR_PTR(rc); | |
bd4cd745 | 125 | return nfit_res->buf + offset - nfit_res->res.start; |
a95c90f1 | 126 | } |
e8d51348 | 127 | return devm_memremap_pages(dev, pgmap); |
979fccfb | 128 | } |
808153e1 | 129 | EXPORT_SYMBOL_GPL(__wrap_devm_memremap_pages); |
979fccfb | 130 | |
76e9f0ee | 131 | pfn_t __wrap_phys_to_pfn_t(phys_addr_t addr, unsigned long flags) |
979fccfb | 132 | { |
9bfa8496 | 133 | struct nfit_test_resource *nfit_res = get_nfit_res(addr); |
979fccfb | 134 | |
979fccfb DW |
135 | if (nfit_res) |
136 | flags &= ~PFN_MAP; | |
137 | return phys_to_pfn_t(addr, flags); | |
138 | } | |
139 | EXPORT_SYMBOL(__wrap_phys_to_pfn_t); | |
979fccfb | 140 | |
67a3e8fe RZ |
141 | void *__wrap_memremap(resource_size_t offset, size_t size, |
142 | unsigned long flags) | |
143 | { | |
9bfa8496 | 144 | struct nfit_test_resource *nfit_res = get_nfit_res(offset); |
67a3e8fe | 145 | |
67a3e8fe | 146 | if (nfit_res) |
bd4cd745 | 147 | return nfit_res->buf + offset - nfit_res->res.start; |
67a3e8fe RZ |
148 | return memremap(offset, size, flags); |
149 | } | |
150 | EXPORT_SYMBOL(__wrap_memremap); | |
151 | ||
32ab0a3f DW |
152 | void __wrap_devm_memunmap(struct device *dev, void *addr) |
153 | { | |
9bfa8496 | 154 | struct nfit_test_resource *nfit_res = get_nfit_res((long) addr); |
32ab0a3f | 155 | |
32ab0a3f DW |
156 | if (nfit_res) |
157 | return; | |
158 | return devm_memunmap(dev, addr); | |
159 | } | |
160 | EXPORT_SYMBOL(__wrap_devm_memunmap); | |
161 | ||
6bc75619 DW |
162 | void __iomem *__wrap_ioremap_nocache(resource_size_t offset, unsigned long size) |
163 | { | |
164 | return __nfit_test_ioremap(offset, size, ioremap_nocache); | |
165 | } | |
166 | EXPORT_SYMBOL(__wrap_ioremap_nocache); | |
167 | ||
9d27a87e DW |
168 | void __iomem *__wrap_ioremap_wc(resource_size_t offset, unsigned long size) |
169 | { | |
170 | return __nfit_test_ioremap(offset, size, ioremap_wc); | |
171 | } | |
172 | EXPORT_SYMBOL(__wrap_ioremap_wc); | |
173 | ||
6bc75619 DW |
174 | void __wrap_iounmap(volatile void __iomem *addr) |
175 | { | |
9bfa8496 | 176 | struct nfit_test_resource *nfit_res = get_nfit_res((long) addr); |
6bc75619 DW |
177 | if (nfit_res) |
178 | return; | |
179 | return iounmap(addr); | |
180 | } | |
181 | EXPORT_SYMBOL(__wrap_iounmap); | |
182 | ||
67a3e8fe RZ |
183 | void __wrap_memunmap(void *addr) |
184 | { | |
9bfa8496 | 185 | struct nfit_test_resource *nfit_res = get_nfit_res((long) addr); |
67a3e8fe | 186 | |
67a3e8fe RZ |
187 | if (nfit_res) |
188 | return; | |
189 | return memunmap(addr); | |
190 | } | |
191 | EXPORT_SYMBOL(__wrap_memunmap); | |
192 | ||
bd4cd745 DW |
193 | static bool nfit_test_release_region(struct device *dev, |
194 | struct resource *parent, resource_size_t start, | |
195 | resource_size_t n); | |
196 | ||
197 | static void nfit_devres_release(struct device *dev, void *data) | |
198 | { | |
199 | struct resource *res = *((struct resource **) data); | |
200 | ||
201 | WARN_ON(!nfit_test_release_region(NULL, &iomem_resource, res->start, | |
202 | resource_size(res))); | |
203 | } | |
204 | ||
205 | static int match(struct device *dev, void *__res, void *match_data) | |
206 | { | |
207 | struct resource *res = *((struct resource **) __res); | |
208 | resource_size_t start = *((resource_size_t *) match_data); | |
209 | ||
210 | return res->start == start; | |
211 | } | |
212 | ||
213 | static bool nfit_test_release_region(struct device *dev, | |
214 | struct resource *parent, resource_size_t start, | |
215 | resource_size_t n) | |
216 | { | |
217 | if (parent == &iomem_resource) { | |
218 | struct nfit_test_resource *nfit_res = get_nfit_res(start); | |
219 | ||
220 | if (nfit_res) { | |
221 | struct nfit_test_request *req; | |
222 | struct resource *res = NULL; | |
223 | ||
224 | if (dev) { | |
225 | devres_release(dev, nfit_devres_release, match, | |
226 | &start); | |
227 | return true; | |
228 | } | |
229 | ||
230 | spin_lock(&nfit_res->lock); | |
231 | list_for_each_entry(req, &nfit_res->requests, list) | |
232 | if (req->res.start == start) { | |
233 | res = &req->res; | |
234 | list_del(&req->list); | |
235 | break; | |
236 | } | |
237 | spin_unlock(&nfit_res->lock); | |
238 | ||
239 | WARN(!res || resource_size(res) != n, | |
240 | "%s: start: %llx n: %llx mismatch: %pr\n", | |
241 | __func__, start, n, res); | |
242 | if (res) | |
243 | kfree(req); | |
244 | return true; | |
245 | } | |
246 | } | |
247 | return false; | |
248 | } | |
249 | ||
708ab62b CH |
250 | static struct resource *nfit_test_request_region(struct device *dev, |
251 | struct resource *parent, resource_size_t start, | |
252 | resource_size_t n, const char *name, int flags) | |
6bc75619 DW |
253 | { |
254 | struct nfit_test_resource *nfit_res; | |
255 | ||
256 | if (parent == &iomem_resource) { | |
6bc75619 | 257 | nfit_res = get_nfit_res(start); |
6bc75619 | 258 | if (nfit_res) { |
bd4cd745 DW |
259 | struct nfit_test_request *req; |
260 | struct resource *res = NULL; | |
6bc75619 | 261 | |
bd4cd745 DW |
262 | if (start + n > nfit_res->res.start |
263 | + resource_size(&nfit_res->res)) { | |
6bc75619 DW |
264 | pr_debug("%s: start: %llx n: %llx overflow: %pr\n", |
265 | __func__, start, n, | |
bd4cd745 DW |
266 | &nfit_res->res); |
267 | return NULL; | |
268 | } | |
269 | ||
270 | spin_lock(&nfit_res->lock); | |
271 | list_for_each_entry(req, &nfit_res->requests, list) | |
272 | if (start == req->res.start) { | |
273 | res = &req->res; | |
274 | break; | |
275 | } | |
276 | spin_unlock(&nfit_res->lock); | |
277 | ||
278 | if (res) { | |
279 | WARN(1, "%pr already busy\n", res); | |
6bc75619 DW |
280 | return NULL; |
281 | } | |
282 | ||
bd4cd745 DW |
283 | req = kzalloc(sizeof(*req), GFP_KERNEL); |
284 | if (!req) | |
285 | return NULL; | |
286 | INIT_LIST_HEAD(&req->list); | |
287 | res = &req->res; | |
288 | ||
6bc75619 DW |
289 | res->start = start; |
290 | res->end = start + n - 1; | |
291 | res->name = name; | |
292 | res->flags = resource_type(parent); | |
293 | res->flags |= IORESOURCE_BUSY | flags; | |
bd4cd745 DW |
294 | spin_lock(&nfit_res->lock); |
295 | list_add(&req->list, &nfit_res->requests); | |
296 | spin_unlock(&nfit_res->lock); | |
297 | ||
298 | if (dev) { | |
299 | struct resource **d; | |
300 | ||
301 | d = devres_alloc(nfit_devres_release, | |
302 | sizeof(struct resource *), | |
303 | GFP_KERNEL); | |
304 | if (!d) | |
305 | return NULL; | |
306 | *d = res; | |
307 | devres_add(dev, d); | |
308 | } | |
309 | ||
6bc75619 DW |
310 | pr_debug("%s: %pr\n", __func__, res); |
311 | return res; | |
312 | } | |
313 | } | |
708ab62b CH |
314 | if (dev) |
315 | return __devm_request_region(dev, parent, start, n, name); | |
6bc75619 DW |
316 | return __request_region(parent, start, n, name, flags); |
317 | } | |
708ab62b CH |
318 | |
319 | struct resource *__wrap___request_region(struct resource *parent, | |
320 | resource_size_t start, resource_size_t n, const char *name, | |
321 | int flags) | |
322 | { | |
323 | return nfit_test_request_region(NULL, parent, start, n, name, flags); | |
324 | } | |
6bc75619 DW |
325 | EXPORT_SYMBOL(__wrap___request_region); |
326 | ||
ee8520fe DW |
327 | int __wrap_insert_resource(struct resource *parent, struct resource *res) |
328 | { | |
329 | if (get_nfit_res(res->start)) | |
330 | return 0; | |
331 | return insert_resource(parent, res); | |
332 | } | |
333 | EXPORT_SYMBOL(__wrap_insert_resource); | |
334 | ||
335 | int __wrap_remove_resource(struct resource *res) | |
336 | { | |
337 | if (get_nfit_res(res->start)) | |
338 | return 0; | |
339 | return remove_resource(res); | |
340 | } | |
341 | EXPORT_SYMBOL(__wrap_remove_resource); | |
342 | ||
708ab62b CH |
343 | struct resource *__wrap___devm_request_region(struct device *dev, |
344 | struct resource *parent, resource_size_t start, | |
345 | resource_size_t n, const char *name) | |
346 | { | |
347 | if (!dev) | |
348 | return NULL; | |
349 | return nfit_test_request_region(dev, parent, start, n, name, 0); | |
350 | } | |
351 | EXPORT_SYMBOL(__wrap___devm_request_region); | |
352 | ||
200c79da DW |
353 | void __wrap___release_region(struct resource *parent, resource_size_t start, |
354 | resource_size_t n) | |
355 | { | |
bd4cd745 | 356 | if (!nfit_test_release_region(NULL, parent, start, n)) |
200c79da | 357 | __release_region(parent, start, n); |
6bc75619 DW |
358 | } |
359 | EXPORT_SYMBOL(__wrap___release_region); | |
360 | ||
200c79da DW |
361 | void __wrap___devm_release_region(struct device *dev, struct resource *parent, |
362 | resource_size_t start, resource_size_t n) | |
363 | { | |
bd4cd745 | 364 | if (!nfit_test_release_region(dev, parent, start, n)) |
200c79da DW |
365 | __devm_release_region(dev, parent, start, n); |
366 | } | |
367 | EXPORT_SYMBOL(__wrap___devm_release_region); | |
368 | ||
c14a868a DW |
369 | acpi_status __wrap_acpi_evaluate_object(acpi_handle handle, acpi_string path, |
370 | struct acpi_object_list *p, struct acpi_buffer *buf) | |
371 | { | |
372 | struct nfit_test_resource *nfit_res = get_nfit_res((long) handle); | |
373 | union acpi_object **obj; | |
374 | ||
375 | if (!nfit_res || strcmp(path, "_FIT") || !buf) | |
376 | return acpi_evaluate_object(handle, path, p, buf); | |
377 | ||
378 | obj = nfit_res->buf; | |
379 | buf->length = sizeof(union acpi_object); | |
380 | buf->pointer = *obj; | |
381 | return AE_OK; | |
382 | } | |
383 | EXPORT_SYMBOL(__wrap_acpi_evaluate_object); | |
384 | ||
94116f81 | 385 | union acpi_object * __wrap_acpi_evaluate_dsm(acpi_handle handle, const guid_t *guid, |
a7de92da DW |
386 | u64 rev, u64 func, union acpi_object *argv4) |
387 | { | |
388 | union acpi_object *obj = ERR_PTR(-ENXIO); | |
389 | struct iomap_ops *ops; | |
390 | ||
391 | rcu_read_lock(); | |
392 | ops = list_first_or_null_rcu(&iomap_head, typeof(*ops), list); | |
393 | if (ops) | |
94116f81 | 394 | obj = ops->evaluate_dsm(handle, guid, rev, func, argv4); |
a7de92da DW |
395 | rcu_read_unlock(); |
396 | ||
397 | if (IS_ERR(obj)) | |
94116f81 | 398 | return acpi_evaluate_dsm(handle, guid, rev, func, argv4); |
a7de92da DW |
399 | return obj; |
400 | } | |
401 | EXPORT_SYMBOL(__wrap_acpi_evaluate_dsm); | |
402 | ||
6bc75619 | 403 | MODULE_LICENSE("GPL v2"); |