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, &reg->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