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