1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2019-2020 Linaro Ltd. |
4 | */ |
5 | #include <linux/kernel.h> |
6 | #include <linux/module.h> |
7 | #include <linux/mutex.h> |
8 | #include <linux/of_address.h> |
9 | #include "qcom_pil_info.h" |
10 | |
11 | /* |
12 | * The PIL relocation information region is used to communicate memory regions |
13 | * occupied by co-processor firmware for post mortem crash analysis. |
14 | * |
15 | * It consists of an array of entries with an 8 byte textual identifier of the |
16 | * region followed by a 64 bit base address and 32 bit size, both little |
17 | * endian. |
18 | */ |
19 | #define PIL_RELOC_NAME_LEN 8 |
20 | #define PIL_RELOC_ENTRY_SIZE (PIL_RELOC_NAME_LEN + sizeof(__le64) + sizeof(__le32)) |
21 | |
22 | struct pil_reloc { |
23 | void __iomem *base; |
24 | size_t num_entries; |
25 | }; |
26 | |
27 | static struct pil_reloc _reloc __read_mostly; |
28 | static DEFINE_MUTEX(pil_reloc_lock); |
29 | |
30 | static int qcom_pil_info_init(void) |
31 | { |
32 | struct device_node *np; |
33 | struct resource imem; |
34 | void __iomem *base; |
35 | int ret; |
36 | |
37 | /* Already initialized? */ |
38 | if (_reloc.base) |
39 | return 0; |
40 | |
41 | np = of_find_compatible_node(NULL, NULL, compat: "qcom,pil-reloc-info" ); |
42 | if (!np) |
43 | return -ENOENT; |
44 | |
45 | ret = of_address_to_resource(dev: np, index: 0, r: &imem); |
46 | of_node_put(node: np); |
47 | if (ret < 0) |
48 | return ret; |
49 | |
50 | base = ioremap(offset: imem.start, size: resource_size(res: &imem)); |
51 | if (!base) { |
52 | pr_err("failed to map PIL relocation info region\n" ); |
53 | return -ENOMEM; |
54 | } |
55 | |
56 | memset_io(base, 0, resource_size(res: &imem)); |
57 | |
58 | _reloc.base = base; |
59 | _reloc.num_entries = (u32)resource_size(res: &imem) / PIL_RELOC_ENTRY_SIZE; |
60 | |
61 | return 0; |
62 | } |
63 | |
64 | /** |
65 | * qcom_pil_info_store() - store PIL information of image in IMEM |
66 | * @image: name of the image |
67 | * @base: base address of the loaded image |
68 | * @size: size of the loaded image |
69 | * |
70 | * Return: 0 on success, negative errno on failure |
71 | */ |
72 | int qcom_pil_info_store(const char *image, phys_addr_t base, size_t size) |
73 | { |
74 | char buf[PIL_RELOC_NAME_LEN]; |
75 | void __iomem *entry; |
76 | int ret; |
77 | int i; |
78 | |
79 | mutex_lock(&pil_reloc_lock); |
80 | ret = qcom_pil_info_init(); |
81 | if (ret < 0) { |
82 | mutex_unlock(lock: &pil_reloc_lock); |
83 | return ret; |
84 | } |
85 | |
86 | for (i = 0; i < _reloc.num_entries; i++) { |
87 | entry = _reloc.base + i * PIL_RELOC_ENTRY_SIZE; |
88 | |
89 | memcpy_fromio(buf, entry, PIL_RELOC_NAME_LEN); |
90 | |
91 | /* |
92 | * An empty record means we didn't find it, given that the |
93 | * records are packed. |
94 | */ |
95 | if (!buf[0]) |
96 | goto found_unused; |
97 | |
98 | if (!strncmp(buf, image, PIL_RELOC_NAME_LEN)) |
99 | goto found_existing; |
100 | } |
101 | |
102 | pr_warn("insufficient PIL info slots\n" ); |
103 | mutex_unlock(lock: &pil_reloc_lock); |
104 | return -ENOMEM; |
105 | |
106 | found_unused: |
107 | memcpy_toio(entry, image, strnlen(p: image, PIL_RELOC_NAME_LEN)); |
108 | found_existing: |
109 | /* Use two writel() as base is only aligned to 4 bytes on odd entries */ |
110 | writel(val: base, addr: entry + PIL_RELOC_NAME_LEN); |
111 | writel(val: (u64)base >> 32, addr: entry + PIL_RELOC_NAME_LEN + 4); |
112 | writel(val: size, addr: entry + PIL_RELOC_NAME_LEN + sizeof(__le64)); |
113 | mutex_unlock(lock: &pil_reloc_lock); |
114 | |
115 | return 0; |
116 | } |
117 | EXPORT_SYMBOL_GPL(qcom_pil_info_store); |
118 | |
119 | static void __exit pil_reloc_exit(void) |
120 | { |
121 | mutex_lock(&pil_reloc_lock); |
122 | iounmap(addr: _reloc.base); |
123 | _reloc.base = NULL; |
124 | mutex_unlock(lock: &pil_reloc_lock); |
125 | } |
126 | module_exit(pil_reloc_exit); |
127 | |
128 | MODULE_DESCRIPTION("Qualcomm PIL relocation info" ); |
129 | MODULE_LICENSE("GPL v2" ); |
130 | |