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