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