]>
Commit | Line | Data |
---|---|---|
39795f9c DW |
1 | /* |
2 | * probe_roms - scan for Adapter ROMS | |
3 | * | |
4 | * (based on linux-2.6:arch/x86/kernel/probe_roms_32.c) | |
5 | * | |
6 | * Copyright (C) 2008 Intel Corporation | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms and conditions of the GNU General Public License, | |
10 | * version 2, as published by the Free Software Foundation. | |
11 | * | |
12 | * This program is distributed in the hope it will be useful, but WITHOUT | |
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
14 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
15 | * more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License along with | |
18 | * this program; if not, write to the Free Software Foundation, Inc., | |
19 | * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | |
20 | */ | |
21 | ||
22 | #include "probe_roms.h" | |
3c8bfb5d | 23 | #include "mdadm.h" |
39795f9c DW |
24 | #include <unistd.h> |
25 | #include <signal.h> | |
26 | #include <fcntl.h> | |
27 | #include <sys/mman.h> | |
28 | #include <sys/stat.h> | |
29 | #include <sys/types.h> | |
30 | #include <asm/types.h> | |
31 | ||
32 | static void *rom_mem = MAP_FAILED; | |
33 | static int rom_fd = -1; | |
f21e18ca | 34 | static const int rom_len = 0xf0000 - 0xc0000; /* option-rom memory region */ |
39795f9c | 35 | static int _sigbus; |
969c2555 DW |
36 | static unsigned long rom_align; |
37 | ||
4ec389e3 RS |
38 | static void roms_deinit(void); |
39 | static int roms_init(void); | |
40 | ||
39795f9c DW |
41 | static void sigbus(int sig) |
42 | { | |
43 | _sigbus = 1; | |
44 | } | |
45 | ||
46 | static int probe_address8(const __u8 *ptr, __u8 *val) | |
47 | { | |
48 | int rc = 0; | |
49 | ||
50 | *val = *ptr; | |
51 | if (_sigbus) | |
52 | rc = -1; | |
53 | _sigbus = 0; | |
54 | ||
55 | return rc; | |
56 | } | |
57 | ||
58 | static int probe_address16(const __u16 *ptr, __u16 *val) | |
59 | { | |
60 | int rc = 0; | |
61 | ||
62 | *val = *ptr; | |
63 | if (_sigbus) | |
64 | rc = -1; | |
65 | _sigbus = 0; | |
66 | ||
67 | return rc; | |
68 | } | |
69 | ||
70 | void probe_roms_exit(void) | |
71 | { | |
72 | signal(SIGBUS, SIG_DFL); | |
73 | if (rom_fd >= 0) { | |
74 | close(rom_fd); | |
75 | rom_fd = -1; | |
76 | } | |
77 | if (rom_mem != MAP_FAILED) { | |
78 | munmap(rom_mem, rom_len); | |
79 | rom_mem = MAP_FAILED; | |
80 | } | |
4ec389e3 | 81 | roms_deinit(); |
39795f9c DW |
82 | } |
83 | ||
969c2555 | 84 | int probe_roms_init(unsigned long align) |
39795f9c | 85 | { |
922f66a9 | 86 | int fd = -1; |
39795f9c DW |
87 | int rc = 0; |
88 | ||
969c2555 DW |
89 | /* valid values are 2048 and 512. 512 is for PCI-3.0 compliant |
90 | * systems, or systems that do not have dangerous/legacy ISA | |
91 | * devices. 2048 should always be safe | |
92 | */ | |
93 | if (align == 512 || align == 2048) | |
94 | rom_align = align; | |
95 | else | |
96 | return -1; | |
97 | ||
4ec389e3 RS |
98 | if (roms_init()) |
99 | return -1; | |
100 | ||
39795f9c DW |
101 | if (signal(SIGBUS, sigbus) == SIG_ERR) |
102 | rc = -1; | |
103 | if (rc == 0) { | |
104 | fd = open("/dev/mem", O_RDONLY); | |
105 | if (fd < 0) | |
106 | rc = -1; | |
107 | } | |
108 | if (rc == 0) { | |
109 | rom_mem = mmap(NULL, rom_len, PROT_READ, MAP_PRIVATE, fd, 0xc0000); | |
110 | if (rom_mem == MAP_FAILED) | |
111 | rc = -1; | |
112 | } | |
113 | ||
114 | if (rc == 0) | |
115 | rom_fd = fd; | |
922f66a9 | 116 | else { |
1011e834 | 117 | if (fd >= 0) |
922f66a9 | 118 | close(fd); |
39795f9c | 119 | probe_roms_exit(); |
922f66a9 | 120 | } |
39795f9c DW |
121 | return rc; |
122 | } | |
123 | ||
124 | /** | |
125 | * isa_bus_to_virt - convert physical address to mmap'd region | |
126 | * @addr - address to convert | |
127 | * | |
128 | * Only valid between a successful call to probe_roms_init and the | |
129 | * corresponding probe_roms_exit | |
130 | */ | |
131 | static void *isa_bus_to_virt(unsigned long addr) | |
132 | { | |
133 | return rom_mem + (addr - 0xc0000); | |
134 | } | |
135 | ||
136 | struct resource { | |
137 | unsigned long start; | |
138 | unsigned long end; | |
3c8bfb5d | 139 | unsigned long data; |
39795f9c | 140 | const char *name; |
4ec389e3 | 141 | struct resource *next; |
39795f9c DW |
142 | }; |
143 | ||
144 | static struct resource system_rom_resource = { | |
145 | .name = "System ROM", | |
146 | .start = 0xf0000, | |
3c8bfb5d | 147 | .data = 0, |
39795f9c DW |
148 | .end = 0xfffff, |
149 | }; | |
150 | ||
151 | static struct resource extension_rom_resource = { | |
152 | .name = "Extension ROM", | |
153 | .start = 0xe0000, | |
3c8bfb5d | 154 | .data = 0, |
39795f9c DW |
155 | .end = 0xeffff, |
156 | }; | |
157 | ||
4ec389e3 | 158 | static struct resource *adapter_rom_resources; |
39795f9c DW |
159 | |
160 | static struct resource video_rom_resource = { | |
1011e834 | 161 | .name = "Video ROM", |
39795f9c | 162 | .start = 0xc0000, |
3c8bfb5d | 163 | .data = 0, |
39795f9c DW |
164 | .end = 0xc7fff, |
165 | }; | |
166 | ||
4ec389e3 RS |
167 | static int roms_init(void) |
168 | { | |
169 | adapter_rom_resources = malloc(sizeof(struct resource)); | |
170 | if (adapter_rom_resources == NULL) | |
171 | return 1; | |
172 | adapter_rom_resources->name = "Adapter ROM"; | |
173 | adapter_rom_resources->start = 0xc8000; | |
174 | adapter_rom_resources->data = 0; | |
175 | adapter_rom_resources->end = 0; | |
176 | adapter_rom_resources->next = NULL; | |
177 | return 0; | |
178 | } | |
179 | ||
180 | static void roms_deinit(void) | |
181 | { | |
182 | struct resource *res; | |
183 | ||
184 | res = adapter_rom_resources; | |
185 | while (res) { | |
186 | struct resource *tmp = res; | |
187 | ||
188 | res = res->next; | |
189 | free(tmp); | |
190 | } | |
191 | } | |
192 | ||
39795f9c DW |
193 | #define ROMSIGNATURE 0xaa55 |
194 | ||
4ec389e3 | 195 | |
39795f9c DW |
196 | static int romsignature(const unsigned char *rom) |
197 | { | |
198 | const unsigned short * const ptr = (const unsigned short *)rom; | |
199 | unsigned short sig = 0; | |
200 | ||
201 | return probe_address16(ptr, &sig) == 0 && sig == ROMSIGNATURE; | |
202 | } | |
203 | ||
204 | static int romchecksum(const unsigned char *rom, unsigned long length) | |
205 | { | |
206 | unsigned char sum, c; | |
207 | ||
208 | for (sum = 0; length && probe_address8(rom++, &c) == 0; length--) | |
209 | sum += c; | |
210 | return !length && !sum; | |
211 | } | |
212 | ||
213 | int scan_adapter_roms(scan_fn fn) | |
214 | { | |
215 | /* let scan_fn examing each of the adapter roms found by probe_roms */ | |
4ec389e3 | 216 | struct resource *res = adapter_rom_resources; |
39795f9c DW |
217 | int found; |
218 | ||
219 | if (rom_fd < 0) | |
220 | return 0; | |
221 | ||
222 | found = 0; | |
4ec389e3 | 223 | while (res) { |
39795f9c DW |
224 | if (res->start) { |
225 | found = fn(isa_bus_to_virt(res->start), | |
3c8bfb5d LM |
226 | isa_bus_to_virt(res->end), |
227 | isa_bus_to_virt(res->data)); | |
39795f9c DW |
228 | if (found) |
229 | break; | |
230 | } else | |
231 | break; | |
4ec389e3 | 232 | res = res->next; |
39795f9c DW |
233 | } |
234 | ||
235 | return found; | |
236 | } | |
237 | ||
969c2555 DW |
238 | static unsigned long align(unsigned long addr, unsigned long alignment) |
239 | { | |
240 | return (addr + alignment - 1) & ~(alignment - 1); | |
241 | } | |
242 | ||
39795f9c DW |
243 | void probe_roms(void) |
244 | { | |
245 | const void *rom; | |
246 | unsigned long start, length, upper; | |
247 | unsigned char c; | |
4ec389e3 | 248 | struct resource *res = adapter_rom_resources; |
3c8bfb5d | 249 | __u16 val=0; |
39795f9c DW |
250 | |
251 | if (rom_fd < 0) | |
252 | return; | |
253 | ||
254 | /* video rom */ | |
4ec389e3 | 255 | upper = res->start; |
969c2555 | 256 | for (start = video_rom_resource.start; start < upper; start += rom_align) { |
39795f9c DW |
257 | rom = isa_bus_to_virt(start); |
258 | if (!romsignature(rom)) | |
259 | continue; | |
260 | ||
261 | video_rom_resource.start = start; | |
262 | ||
263 | if (probe_address8(rom + 2, &c) != 0) | |
264 | continue; | |
265 | ||
266 | /* 0 < length <= 0x7f * 512, historically */ | |
267 | length = c * 512; | |
268 | ||
269 | /* if checksum okay, trust length byte */ | |
270 | if (length && romchecksum(rom, length)) | |
271 | video_rom_resource.end = start + length - 1; | |
272 | break; | |
273 | } | |
274 | ||
969c2555 | 275 | start = align(video_rom_resource.end + 1, rom_align); |
39795f9c DW |
276 | if (start < upper) |
277 | start = upper; | |
278 | ||
279 | /* system rom */ | |
280 | upper = system_rom_resource.start; | |
281 | ||
282 | /* check for extension rom (ignore length byte!) */ | |
283 | rom = isa_bus_to_virt(extension_rom_resource.start); | |
284 | if (romsignature(rom)) { | |
285 | length = extension_rom_resource.end - extension_rom_resource.start + 1; | |
286 | if (romchecksum(rom, length)) | |
287 | upper = extension_rom_resource.start; | |
288 | } | |
289 | ||
4ec389e3 | 290 | struct resource *prev_res = res; |
39795f9c | 291 | /* check for adapter roms on 2k boundaries */ |
4ec389e3 | 292 | for (; start < upper; start += rom_align) { |
39795f9c DW |
293 | rom = isa_bus_to_virt(start); |
294 | if (!romsignature(rom)) | |
295 | continue; | |
296 | ||
297 | if (probe_address8(rom + 2, &c) != 0) | |
298 | continue; | |
299 | ||
300 | /* 0 < length <= 0x7f * 512, historically */ | |
301 | length = c * 512; | |
302 | ||
3c8bfb5d LM |
303 | /* Retrieve 16-bit pointer to PCI Data Structure (offset 18h-19h) |
304 | * The data can be within 64KB forward of the first location | |
305 | * of this code image. The pointer is in little-endian order | |
306 | */ | |
307 | ||
308 | if (probe_address16(rom + 0x18, &val) != 0) | |
309 | continue; | |
310 | val = __le16_to_cpu(val); | |
311 | ||
39795f9c DW |
312 | /* but accept any length that fits if checksum okay */ |
313 | if (!length || start + length > upper || !romchecksum(rom, length)) | |
314 | continue; | |
315 | ||
4ec389e3 RS |
316 | if (res == NULL) { |
317 | res = calloc(1, sizeof(struct resource)); | |
318 | if (res == NULL) | |
319 | return; | |
320 | prev_res->next = res; | |
321 | } | |
322 | ||
323 | res->start = start; | |
324 | res->data = start + (unsigned long)val; | |
325 | res->end = start + length - 1; | |
39795f9c | 326 | |
4ec389e3 RS |
327 | start = res->end & ~(rom_align - 1); |
328 | prev_res = res; | |
329 | res = res->next; | |
39795f9c DW |
330 | } |
331 | } |