1 /*
2 * apei-io.c - APEI IO memory pre-mapping/post-unmapping and access
3 *
4 * Copyright (C) 2009-2010, Intel Corp.
5 * Author: Huang Ying <ying.huang@intel.com>
6 * Ported by: Liu, Jinsong <jinsong.liu@intel.com>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License version
10 * 2 as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <xen/kernel.h>
22 #include <xen/errno.h>
23 #include <xen/delay.h>
24 #include <xen/init.h>
25 #include <xen/string.h>
26 #include <xen/xmalloc.h>
27 #include <xen/types.h>
28 #include <xen/spinlock.h>
29 #include <xen/list.h>
30 #include <xen/cper.h>
31 #include <xen/prefetch.h>
32 #include <asm/fixmap.h>
33 #include <asm/io.h>
34 #include <acpi/acpi.h>
35 #include <acpi/apei.h>
36
37 static LIST_HEAD(apei_iomaps);
38 /*
39 * Used for mutual exclusion between writers of apei_iomaps list, for
40 * synchronization between readers and writer.
41 */
42 static DEFINE_SPINLOCK(apei_iomaps_lock);
43
44 struct apei_iomap {
45 struct list_head list;
46 void __iomem *vaddr;
47 unsigned long size;
48 paddr_t paddr;
49 };
50
__apei_find_iomap(paddr_t paddr,unsigned long size)51 static struct apei_iomap *__apei_find_iomap(paddr_t paddr,
52 unsigned long size)
53 {
54 struct apei_iomap *map;
55
56 list_for_each_entry(map, &apei_iomaps, list) {
57 if (map->paddr + map->size >= paddr + size &&
58 map->paddr <= paddr)
59 return map;
60 }
61 return NULL;
62 }
63
__apei_ioremap_fast(paddr_t paddr,unsigned long size)64 static void __iomem *__apei_ioremap_fast(paddr_t paddr,
65 unsigned long size)
66 {
67 struct apei_iomap *map;
68
69 map = __apei_find_iomap(paddr, size);
70 if (map)
71 return map->vaddr + (paddr - map->paddr);
72 else
73 return NULL;
74 }
75
76 static int apei_range_nr;
77
apei_range_map(paddr_t paddr,unsigned long size)78 static void __iomem *__init apei_range_map(paddr_t paddr, unsigned long size)
79 {
80 int i, pg;
81 int start_nr, cur_nr;
82
83 pg = ((((paddr + size -1) & PAGE_MASK)
84 - (paddr & PAGE_MASK)) >> PAGE_SHIFT) + 1;
85 if (apei_range_nr + pg > FIX_APEI_RANGE_MAX)
86 return NULL;
87
88 start_nr = apei_range_nr + pg -1;
89 for (i = 0; i < pg; i++) {
90 cur_nr = start_nr - i;
91 set_fixmap_nocache(FIX_APEI_RANGE_BASE + cur_nr,
92 paddr + (i << PAGE_SHIFT));
93 apei_range_nr++;
94 }
95
96 return fix_to_virt(FIX_APEI_RANGE_BASE + start_nr);
97 }
98
99 /*
100 * Used to pre-map the specified IO memory area. First try to find
101 * whether the area is already pre-mapped, if it is, return; otherwise,
102 * do the real map, and add the mapping into apei_iomaps list.
103 */
apei_pre_map(paddr_t paddr,unsigned long size)104 void __iomem *__init apei_pre_map(paddr_t paddr, unsigned long size)
105 {
106 void __iomem *vaddr;
107 struct apei_iomap *map;
108 unsigned long flags;
109
110 spin_lock_irqsave(&apei_iomaps_lock, flags);
111 vaddr = __apei_ioremap_fast(paddr, size);
112 spin_unlock_irqrestore(&apei_iomaps_lock, flags);
113 if (vaddr)
114 return vaddr;
115
116 map = xmalloc(struct apei_iomap);
117 if (!map)
118 return NULL;
119
120 vaddr = apei_range_map(paddr, size);
121 if (!vaddr) {
122 xfree(map);
123 return NULL;
124 }
125
126 INIT_LIST_HEAD(&map->list);
127 map->paddr = paddr & PAGE_MASK;
128 map->size = (((paddr + size + PAGE_SIZE -1) & PAGE_MASK)
129 - (paddr & PAGE_MASK));
130 map->vaddr = vaddr;
131
132 spin_lock_irqsave(&apei_iomaps_lock, flags);
133 list_add_tail(&map->list, &apei_iomaps);
134 spin_unlock_irqrestore(&apei_iomaps_lock, flags);
135
136 return map->vaddr + (paddr - map->paddr);
137 }
138
139 /*
140 * Used to post-unmap the specified IO memory area.
141 */
apei_post_unmap(paddr_t paddr,unsigned long size)142 static void __init apei_post_unmap(paddr_t paddr, unsigned long size)
143 {
144 struct apei_iomap *map;
145 unsigned long flags;
146
147 spin_lock_irqsave(&apei_iomaps_lock, flags);
148 map = __apei_find_iomap(paddr, size);
149 if (map)
150 list_del(&map->list);
151 spin_unlock_irqrestore(&apei_iomaps_lock, flags);
152
153 xfree(map);
154 }
155
156 /* In NMI handler, should set silent = 1 */
apei_check_gar(struct acpi_generic_address * reg,u64 * paddr,int silent)157 static int apei_check_gar(struct acpi_generic_address *reg,
158 u64 *paddr, int silent)
159 {
160 u32 width, space_id;
161
162 width = reg->bit_width;
163 space_id = reg->space_id;
164 /* Handle possible alignment issues */
165 memcpy(paddr, ®->address, sizeof(*paddr));
166 if (!*paddr) {
167 if (!silent)
168 printk(KERN_WARNING
169 "Invalid physical address in GAR\n");
170 return -EINVAL;
171 }
172
173 if ((width != 8) && (width != 16) && (width != 32) && (width != 64)) {
174 if (!silent)
175 printk(KERN_WARNING
176 "Invalid bit width in GAR\n");
177 return -EINVAL;
178 }
179
180 if (space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY &&
181 space_id != ACPI_ADR_SPACE_SYSTEM_IO) {
182 if (!silent)
183 printk(KERN_WARNING
184 "Invalid address space type in GAR\n");
185 return -EINVAL;
186 }
187
188 return 0;
189 }
190
191 /* Pre-map, working on GAR */
apei_pre_map_gar(struct acpi_generic_address * reg)192 int __init apei_pre_map_gar(struct acpi_generic_address *reg)
193 {
194 u64 paddr;
195 void __iomem *vaddr;
196 int rc;
197
198 if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY)
199 return 0;
200
201 rc = apei_check_gar(reg, &paddr, 0);
202 if (rc)
203 return rc;
204
205 vaddr = apei_pre_map(paddr, reg->bit_width / 8);
206 if (!vaddr)
207 return -EIO;
208
209 return 0;
210 }
211
212 /* Post-unmap, working on GAR */
apei_post_unmap_gar(struct acpi_generic_address * reg)213 int __init apei_post_unmap_gar(struct acpi_generic_address *reg)
214 {
215 u64 paddr;
216 int rc;
217
218 if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY)
219 return 0;
220
221 rc = apei_check_gar(reg, &paddr, 0);
222 if (rc)
223 return rc;
224
225 apei_post_unmap(paddr, reg->bit_width / 8);
226
227 return 0;
228 }
229
apei_read_mem(u64 paddr,u64 * val,u32 width)230 static int apei_read_mem(u64 paddr, u64 *val, u32 width)
231 {
232 void __iomem *addr;
233 u64 tmpval;
234
235 addr = __apei_ioremap_fast(paddr, width);
236 switch (width) {
237 case 8:
238 *val = readb(addr);
239 break;
240 case 16:
241 *val = readw(addr);
242 break;
243 case 32:
244 *val = readl(addr);
245 break;
246 case 64:
247 tmpval = (u64)readl(addr);
248 tmpval |= ((u64)readl(addr+4)) << 32;
249 *val = tmpval;
250 break;
251 default:
252 return -EINVAL;
253 }
254
255 return 0;
256 }
257
apei_write_mem(u64 paddr,u64 val,u32 width)258 static int apei_write_mem(u64 paddr, u64 val, u32 width)
259 {
260 void __iomem *addr;
261 u32 tmpval;
262
263 addr = __apei_ioremap_fast(paddr, width);
264 switch (width) {
265 case 8:
266 writeb(val, addr);
267 break;
268 case 16:
269 writew(val, addr);
270 break;
271 case 32:
272 writel(val, addr);
273 break;
274 case 64:
275 tmpval = (u32)val;
276 writel(tmpval, addr);
277 tmpval = (u32)(val >> 32);
278 writel(tmpval, addr+4);
279 break;
280 default:
281 return -EINVAL;
282 }
283
284 return 0;
285 }
286
apei_read(u64 * val,struct acpi_generic_address * reg)287 int apei_read(u64 *val, struct acpi_generic_address *reg)
288 {
289 u64 paddr;
290 int rc;
291
292 rc = apei_check_gar(reg, &paddr, 1);
293 if (rc)
294 return rc;
295
296 *val = 0;
297
298 /* currently all erst implementation take bit_width as real range */
299 switch (reg->space_id) {
300 case ACPI_ADR_SPACE_SYSTEM_MEMORY:
301 return apei_read_mem(paddr, val, reg->bit_width);
302 case ACPI_ADR_SPACE_SYSTEM_IO:
303 return acpi_os_read_port(paddr, (u32 *)val, reg->bit_width);
304 default:
305 return -EINVAL;
306 }
307 }
308
apei_write(u64 val,struct acpi_generic_address * reg)309 int apei_write(u64 val, struct acpi_generic_address *reg)
310 {
311 u64 paddr;
312 int rc;
313
314 rc = apei_check_gar(reg, &paddr, 1);
315 if (rc)
316 return rc;
317
318 switch (reg->space_id) {
319 case ACPI_ADR_SPACE_SYSTEM_MEMORY:
320 return apei_write_mem(paddr, val, reg->bit_width);
321 case ACPI_ADR_SPACE_SYSTEM_IO:
322 return acpi_os_write_port(paddr, val, reg->bit_width);
323 default:
324 return -EINVAL;
325 }
326 }
327