]>
Commit | Line | Data |
---|---|---|
70c0f263 BS |
1 | /* |
2 | * Copyright 2012 Red Hat Inc. | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
20 | * OTHER DEALINGS IN THE SOFTWARE. | |
21 | * | |
22 | * Authors: Ben Skeggs | |
23 | */ | |
24 | ||
25 | #include <core/object.h> | |
26 | #include <core/device.h> | |
27 | #include <core/subdev.h> | |
28 | #include <core/option.h> | |
29 | ||
30 | #include <subdev/bios.h> | |
31 | #include <subdev/bios/bmp.h> | |
32 | #include <subdev/bios/bit.h> | |
33 | ||
34 | u8 | |
35 | nvbios_checksum(const u8 *data, int size) | |
36 | { | |
37 | u8 sum = 0; | |
38 | while (size--) | |
39 | sum += *data++; | |
40 | return sum; | |
41 | } | |
42 | ||
43 | u16 | |
44 | nvbios_findstr(const u8 *data, int size, const char *str, int len) | |
45 | { | |
46 | int i, j; | |
47 | ||
48 | for (i = 0; i <= (size - len); i++) { | |
49 | for (j = 0; j < len; j++) | |
50 | if ((char)data[i + j] != str[j]) | |
51 | break; | |
52 | if (j == len) | |
53 | return i; | |
54 | } | |
55 | ||
56 | return 0; | |
57 | } | |
58 | ||
77145f1c BS |
59 | #if defined(__powerpc__) |
60 | static void | |
61 | nouveau_bios_shadow_of(struct nouveau_bios *bios) | |
62 | { | |
63 | struct pci_dev *pdev = nv_device(bios)->pdev; | |
64 | struct device_node *dn; | |
65 | const u32 *data; | |
fced4b22 | 66 | int size; |
77145f1c BS |
67 | |
68 | dn = pci_device_to_OF_node(pdev); | |
69 | if (!dn) { | |
70 | nv_info(bios, "Unable to get the OF node\n"); | |
71 | return; | |
72 | } | |
73 | ||
74 | data = of_get_property(dn, "NVDA,BMP", &size); | |
00e4845b | 75 | if (data && size) { |
77145f1c BS |
76 | bios->size = size; |
77 | bios->data = kmalloc(bios->size, GFP_KERNEL); | |
78 | if (bios->data) | |
79 | memcpy(bios->data, data, size); | |
80 | } | |
81 | } | |
82 | #endif | |
83 | ||
70c0f263 BS |
84 | static void |
85 | nouveau_bios_shadow_pramin(struct nouveau_bios *bios) | |
86 | { | |
87 | struct nouveau_device *device = nv_device(bios); | |
791dc143 | 88 | u64 addr = 0; |
70c0f263 BS |
89 | u32 bar0 = 0; |
90 | int i; | |
91 | ||
92 | if (device->card_type >= NV_50) { | |
13a49a10 BS |
93 | if (device->card_type >= NV_C0 && device->card_type < GM100) { |
94 | if (nv_rd32(bios, 0x022500) & 0x00000001) | |
95 | return; | |
96 | } else | |
97 | if (device->card_type >= GM100) { | |
98 | if (nv_rd32(bios, 0x021c04) & 0x00000001) | |
99 | return; | |
100 | } | |
791dc143 | 101 | |
457e77b2 BS |
102 | addr = nv_rd32(bios, 0x619f04); |
103 | if (!(addr & 0x00000008)) { | |
104 | nv_debug(bios, "... not enabled\n"); | |
105 | return; | |
106 | } | |
107 | if ( (addr & 0x00000003) != 1) { | |
108 | nv_debug(bios, "... not in vram\n"); | |
109 | return; | |
110 | } | |
111 | ||
112 | addr = (u64)(addr >> 8) << 8; | |
70c0f263 BS |
113 | if (!addr) { |
114 | addr = (u64)nv_rd32(bios, 0x001700) << 16; | |
115 | addr += 0xf0000; | |
116 | } | |
117 | ||
118 | bar0 = nv_mask(bios, 0x001700, 0xffffffff, addr >> 16); | |
119 | } | |
120 | ||
121 | /* bail if no rom signature */ | |
122 | if (nv_rd08(bios, 0x700000) != 0x55 || | |
123 | nv_rd08(bios, 0x700001) != 0xaa) | |
124 | goto out; | |
125 | ||
126 | bios->size = nv_rd08(bios, 0x700002) * 512; | |
00e4845b MS |
127 | if (!bios->size) |
128 | goto out; | |
129 | ||
70c0f263 BS |
130 | bios->data = kmalloc(bios->size, GFP_KERNEL); |
131 | if (bios->data) { | |
132 | for (i = 0; i < bios->size; i++) | |
133 | nv_wo08(bios, i, nv_rd08(bios, 0x700000 + i)); | |
134 | } | |
135 | ||
136 | out: | |
137 | if (device->card_type >= NV_50) | |
138 | nv_wr32(bios, 0x001700, bar0); | |
139 | } | |
140 | ||
141 | static void | |
142 | nouveau_bios_shadow_prom(struct nouveau_bios *bios) | |
143 | { | |
144 | struct nouveau_device *device = nv_device(bios); | |
145 | u32 pcireg, access; | |
146 | u16 pcir; | |
147 | int i; | |
148 | ||
5ac607ec IM |
149 | /* there is no prom on nv4x IGP's */ |
150 | if (device->card_type == NV_40 && device->chipset >= 0x4c) | |
151 | return; | |
152 | ||
70c0f263 BS |
153 | /* enable access to rom */ |
154 | if (device->card_type >= NV_50) | |
155 | pcireg = 0x088050; | |
156 | else | |
157 | pcireg = 0x001850; | |
158 | access = nv_mask(bios, pcireg, 0x00000001, 0x00000000); | |
159 | ||
18acc6d8 MP |
160 | /* WARNING: PROM accesses should always be 32-bits aligned. Other |
161 | * accesses work on most chipset but do not on Kepler chipsets | |
162 | */ | |
163 | ||
70c0f263 BS |
164 | /* bail if no rom signature, with a workaround for a PROM reading |
165 | * issue on some chipsets. the first read after a period of | |
166 | * inactivity returns the wrong result, so retry the first header | |
167 | * byte a few times before giving up as a workaround | |
168 | */ | |
169 | i = 16; | |
170 | do { | |
18acc6d8 | 171 | if ((nv_rd32(bios, 0x300000) & 0xffff) == 0xaa55) |
70c0f263 BS |
172 | break; |
173 | } while (i--); | |
174 | ||
18acc6d8 | 175 | if (!i) |
70c0f263 BS |
176 | goto out; |
177 | ||
178 | /* read entire bios image to system memory */ | |
18acc6d8 | 179 | bios->size = ((nv_rd32(bios, 0x300000) >> 16) & 0xff) * 512; |
00e4845b MS |
180 | if (!bios->size) |
181 | goto out; | |
182 | ||
70c0f263 BS |
183 | bios->data = kmalloc(bios->size, GFP_KERNEL); |
184 | if (bios->data) { | |
18acc6d8 MP |
185 | for (i = 0; i < bios->size; i+=4) |
186 | nv_wo32(bios, i, nv_rd32(bios, 0x300000 + i)); | |
187 | } | |
188 | ||
189 | /* check the PCI record header */ | |
190 | pcir = nv_ro16(bios, 0x0018); | |
191 | if (bios->data[pcir + 0] != 'P' || | |
192 | bios->data[pcir + 1] != 'C' || | |
193 | bios->data[pcir + 2] != 'I' || | |
194 | bios->data[pcir + 3] != 'R') { | |
195 | bios->size = 0; | |
196 | kfree(bios->data); | |
70c0f263 BS |
197 | } |
198 | ||
199 | out: | |
200 | /* disable access to rom */ | |
201 | nv_wr32(bios, pcireg, access); | |
202 | } | |
203 | ||
a91ed42d | 204 | #if defined(CONFIG_ACPI) && defined(CONFIG_X86) |
70c0f263 BS |
205 | int nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len); |
206 | bool nouveau_acpi_rom_supported(struct pci_dev *pdev); | |
207 | #else | |
208 | static inline bool | |
209 | nouveau_acpi_rom_supported(struct pci_dev *pdev) { | |
210 | return false; | |
211 | } | |
212 | ||
213 | static inline int | |
214 | nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len) { | |
215 | return -EINVAL; | |
216 | } | |
217 | #endif | |
218 | ||
219 | static void | |
220 | nouveau_bios_shadow_acpi(struct nouveau_bios *bios) | |
221 | { | |
222 | struct pci_dev *pdev = nv_device(bios)->pdev; | |
9a334cd0 | 223 | int ret, cnt, i; |
70c0f263 | 224 | |
90e2889c MP |
225 | if (!nouveau_acpi_rom_supported(pdev)) { |
226 | bios->data = NULL; | |
70c0f263 | 227 | return; |
90e2889c | 228 | } |
70c0f263 | 229 | |
70c0f263 | 230 | bios->size = 0; |
d1626a96 BS |
231 | bios->data = kmalloc(4096, GFP_KERNEL); |
232 | if (bios->data) { | |
233 | if (nouveau_acpi_get_bios_chunk(bios->data, 0, 4096) == 4096) | |
234 | bios->size = bios->data[2] * 512; | |
235 | kfree(bios->data); | |
236 | } | |
237 | ||
00e4845b MS |
238 | if (!bios->size) |
239 | return; | |
70c0f263 | 240 | |
9a334cd0 | 241 | bios->data = kmalloc(bios->size, GFP_KERNEL); |
de2b8b8b BS |
242 | if (bios->data) { |
243 | /* disobey the acpi spec - much faster on at least w530 ... */ | |
244 | ret = nouveau_acpi_get_bios_chunk(bios->data, 0, bios->size); | |
245 | if (ret != bios->size || | |
246 | nvbios_checksum(bios->data, bios->size)) { | |
247 | /* ... that didn't work, ok, i'll be good now */ | |
248 | for (i = 0; i < bios->size; i += cnt) { | |
249 | cnt = min((bios->size - i), (u32)4096); | |
250 | ret = nouveau_acpi_get_bios_chunk(bios->data, i, cnt); | |
251 | if (ret != cnt) | |
252 | break; | |
253 | } | |
254 | } | |
70c0f263 BS |
255 | } |
256 | } | |
257 | ||
258 | static void | |
259 | nouveau_bios_shadow_pci(struct nouveau_bios *bios) | |
260 | { | |
261 | struct pci_dev *pdev = nv_device(bios)->pdev; | |
262 | size_t size; | |
263 | ||
264 | if (!pci_enable_rom(pdev)) { | |
265 | void __iomem *rom = pci_map_rom(pdev, &size); | |
266 | if (rom && size) { | |
267 | bios->data = kmalloc(size, GFP_KERNEL); | |
268 | if (bios->data) { | |
269 | memcpy_fromio(bios->data, rom, size); | |
270 | bios->size = size; | |
271 | } | |
272 | } | |
273 | if (rom) | |
274 | pci_unmap_rom(pdev, rom); | |
275 | ||
276 | pci_disable_rom(pdev); | |
277 | } | |
278 | } | |
279 | ||
121cdf08 MG |
280 | static void |
281 | nouveau_bios_shadow_platform(struct nouveau_bios *bios) | |
282 | { | |
283 | struct pci_dev *pdev = nv_device(bios)->pdev; | |
284 | size_t size; | |
285 | ||
286 | void __iomem *rom = pci_platform_rom(pdev, &size); | |
287 | if (rom && size) { | |
288 | bios->data = kmalloc(size, GFP_KERNEL); | |
289 | if (bios->data) { | |
290 | memcpy_fromio(bios->data, rom, size); | |
291 | bios->size = size; | |
292 | } | |
293 | } | |
294 | } | |
295 | ||
70c0f263 BS |
296 | static int |
297 | nouveau_bios_score(struct nouveau_bios *bios, const bool writeable) | |
298 | { | |
00e4845b MS |
299 | if (bios->size < 3 || !bios->data || bios->data[0] != 0x55 || |
300 | bios->data[1] != 0xAA) { | |
70c0f263 BS |
301 | nv_info(bios, "... signature not found\n"); |
302 | return 0; | |
303 | } | |
304 | ||
00e4845b MS |
305 | if (nvbios_checksum(bios->data, |
306 | min_t(u32, bios->data[2] * 512, bios->size))) { | |
70c0f263 BS |
307 | nv_info(bios, "... checksum invalid\n"); |
308 | /* if a ro image is somewhat bad, it's probably all rubbish */ | |
309 | return writeable ? 2 : 1; | |
310 | } | |
311 | ||
312 | nv_info(bios, "... appears to be valid\n"); | |
313 | return 3; | |
314 | } | |
315 | ||
316 | struct methods { | |
77145f1c | 317 | const char desc[16]; |
70c0f263 BS |
318 | void (*shadow)(struct nouveau_bios *); |
319 | const bool rw; | |
320 | int score; | |
321 | u32 size; | |
322 | u8 *data; | |
323 | }; | |
324 | ||
325 | static int | |
326 | nouveau_bios_shadow(struct nouveau_bios *bios) | |
327 | { | |
328 | struct methods shadow_methods[] = { | |
77145f1c BS |
329 | #if defined(__powerpc__) |
330 | { "OpenFirmware", nouveau_bios_shadow_of, true, 0, 0, NULL }, | |
331 | #endif | |
70c0f263 BS |
332 | { "PRAMIN", nouveau_bios_shadow_pramin, true, 0, 0, NULL }, |
333 | { "PROM", nouveau_bios_shadow_prom, false, 0, 0, NULL }, | |
334 | { "ACPI", nouveau_bios_shadow_acpi, true, 0, 0, NULL }, | |
335 | { "PCIROM", nouveau_bios_shadow_pci, true, 0, 0, NULL }, | |
121cdf08 | 336 | { "PLATFORM", nouveau_bios_shadow_platform, true, 0, 0, NULL }, |
70c0f263 BS |
337 | {} |
338 | }; | |
339 | struct methods *mthd, *best; | |
340 | const struct firmware *fw; | |
341 | const char *optarg; | |
342 | int optlen, ret; | |
343 | char *source; | |
344 | ||
345 | optarg = nouveau_stropt(nv_device(bios)->cfgopt, "NvBios", &optlen); | |
346 | source = optarg ? kstrndup(optarg, optlen, GFP_KERNEL) : NULL; | |
347 | if (source) { | |
348 | /* try to match one of the built-in methods */ | |
349 | mthd = shadow_methods; | |
350 | do { | |
351 | if (strcasecmp(source, mthd->desc)) | |
352 | continue; | |
353 | nv_info(bios, "source: %s\n", mthd->desc); | |
354 | ||
355 | mthd->shadow(bios); | |
356 | mthd->score = nouveau_bios_score(bios, mthd->rw); | |
357 | if (mthd->score) { | |
358 | kfree(source); | |
359 | return 0; | |
360 | } | |
361 | } while ((++mthd)->shadow); | |
362 | ||
363 | /* attempt to load firmware image */ | |
364 | ret = request_firmware(&fw, source, &nv_device(bios)->pdev->dev); | |
365 | if (ret == 0) { | |
366 | bios->size = fw->size; | |
367 | bios->data = kmemdup(fw->data, fw->size, GFP_KERNEL); | |
368 | release_firmware(fw); | |
369 | ||
370 | nv_info(bios, "image: %s\n", source); | |
371 | if (nouveau_bios_score(bios, 1)) { | |
372 | kfree(source); | |
373 | return 0; | |
374 | } | |
375 | ||
376 | kfree(bios->data); | |
377 | bios->data = NULL; | |
378 | } | |
379 | ||
380 | nv_error(bios, "source \'%s\' invalid\n", source); | |
381 | kfree(source); | |
382 | } | |
383 | ||
384 | mthd = shadow_methods; | |
385 | do { | |
386 | nv_info(bios, "checking %s for image...\n", mthd->desc); | |
387 | mthd->shadow(bios); | |
388 | mthd->score = nouveau_bios_score(bios, mthd->rw); | |
389 | mthd->size = bios->size; | |
390 | mthd->data = bios->data; | |
391 | bios->data = NULL; | |
392 | } while (mthd->score != 3 && (++mthd)->shadow); | |
393 | ||
394 | mthd = shadow_methods; | |
395 | best = mthd; | |
396 | do { | |
397 | if (mthd->score > best->score) { | |
398 | kfree(best->data); | |
399 | best = mthd; | |
400 | } | |
401 | } while ((++mthd)->shadow); | |
402 | ||
403 | if (best->score) { | |
404 | nv_info(bios, "using image from %s\n", best->desc); | |
405 | bios->size = best->size; | |
406 | bios->data = best->data; | |
407 | return 0; | |
408 | } | |
409 | ||
410 | nv_error(bios, "unable to locate usable image\n"); | |
411 | return -EINVAL; | |
412 | } | |
413 | ||
414 | static u8 | |
0a32241d | 415 | nouveau_bios_rd08(struct nouveau_object *object, u64 addr) |
70c0f263 BS |
416 | { |
417 | struct nouveau_bios *bios = (void *)object; | |
418 | return bios->data[addr]; | |
419 | } | |
420 | ||
421 | static u16 | |
0a32241d | 422 | nouveau_bios_rd16(struct nouveau_object *object, u64 addr) |
70c0f263 BS |
423 | { |
424 | struct nouveau_bios *bios = (void *)object; | |
425 | return get_unaligned_le16(&bios->data[addr]); | |
426 | } | |
427 | ||
428 | static u32 | |
0a32241d | 429 | nouveau_bios_rd32(struct nouveau_object *object, u64 addr) |
70c0f263 BS |
430 | { |
431 | struct nouveau_bios *bios = (void *)object; | |
432 | return get_unaligned_le32(&bios->data[addr]); | |
433 | } | |
434 | ||
435 | static void | |
0a32241d | 436 | nouveau_bios_wr08(struct nouveau_object *object, u64 addr, u8 data) |
70c0f263 BS |
437 | { |
438 | struct nouveau_bios *bios = (void *)object; | |
439 | bios->data[addr] = data; | |
440 | } | |
441 | ||
442 | static void | |
0a32241d | 443 | nouveau_bios_wr16(struct nouveau_object *object, u64 addr, u16 data) |
70c0f263 BS |
444 | { |
445 | struct nouveau_bios *bios = (void *)object; | |
446 | put_unaligned_le16(data, &bios->data[addr]); | |
447 | } | |
448 | ||
449 | static void | |
0a32241d | 450 | nouveau_bios_wr32(struct nouveau_object *object, u64 addr, u32 data) |
70c0f263 BS |
451 | { |
452 | struct nouveau_bios *bios = (void *)object; | |
453 | put_unaligned_le32(data, &bios->data[addr]); | |
454 | } | |
455 | ||
456 | static int | |
457 | nouveau_bios_ctor(struct nouveau_object *parent, | |
458 | struct nouveau_object *engine, | |
459 | struct nouveau_oclass *oclass, void *data, u32 size, | |
460 | struct nouveau_object **pobject) | |
461 | { | |
462 | struct nouveau_bios *bios; | |
463 | struct bit_entry bit_i; | |
464 | int ret; | |
465 | ||
466 | ret = nouveau_subdev_create(parent, engine, oclass, 0, | |
467 | "VBIOS", "bios", &bios); | |
468 | *pobject = nv_object(bios); | |
469 | if (ret) | |
470 | return ret; | |
471 | ||
472 | ret = nouveau_bios_shadow(bios); | |
473 | if (ret) | |
474 | return ret; | |
475 | ||
476 | /* detect type of vbios we're dealing with */ | |
477 | bios->bmp_offset = nvbios_findstr(bios->data, bios->size, | |
478 | "\xff\x7f""NV\0", 5); | |
479 | if (bios->bmp_offset) { | |
480 | nv_info(bios, "BMP version %x.%x\n", | |
481 | bmp_version(bios) >> 8, | |
482 | bmp_version(bios) & 0xff); | |
483 | } | |
484 | ||
485 | bios->bit_offset = nvbios_findstr(bios->data, bios->size, | |
486 | "\xff\xb8""BIT", 5); | |
487 | if (bios->bit_offset) | |
488 | nv_info(bios, "BIT signature found\n"); | |
489 | ||
490 | /* determine the vbios version number */ | |
491 | if (!bit_entry(bios, 'i', &bit_i) && bit_i.length >= 4) { | |
492 | bios->version.major = nv_ro08(bios, bit_i.offset + 3); | |
493 | bios->version.chip = nv_ro08(bios, bit_i.offset + 2); | |
494 | bios->version.minor = nv_ro08(bios, bit_i.offset + 1); | |
495 | bios->version.micro = nv_ro08(bios, bit_i.offset + 0); | |
0dd660d4 | 496 | bios->version.patch = nv_ro08(bios, bit_i.offset + 4); |
70c0f263 BS |
497 | } else |
498 | if (bmp_version(bios)) { | |
499 | bios->version.major = nv_ro08(bios, bios->bmp_offset + 13); | |
500 | bios->version.chip = nv_ro08(bios, bios->bmp_offset + 12); | |
501 | bios->version.minor = nv_ro08(bios, bios->bmp_offset + 11); | |
502 | bios->version.micro = nv_ro08(bios, bios->bmp_offset + 10); | |
503 | } | |
504 | ||
0dd660d4 | 505 | nv_info(bios, "version %02x.%02x.%02x.%02x.%02x\n", |
70c0f263 | 506 | bios->version.major, bios->version.chip, |
0dd660d4 | 507 | bios->version.minor, bios->version.micro, bios->version.patch); |
70c0f263 BS |
508 | |
509 | return 0; | |
510 | } | |
511 | ||
512 | static void | |
513 | nouveau_bios_dtor(struct nouveau_object *object) | |
514 | { | |
515 | struct nouveau_bios *bios = (void *)object; | |
516 | kfree(bios->data); | |
517 | nouveau_subdev_destroy(&bios->base); | |
518 | } | |
519 | ||
520 | static int | |
521 | nouveau_bios_init(struct nouveau_object *object) | |
522 | { | |
523 | struct nouveau_bios *bios = (void *)object; | |
524 | return nouveau_subdev_init(&bios->base); | |
525 | } | |
526 | ||
527 | static int | |
528 | nouveau_bios_fini(struct nouveau_object *object, bool suspend) | |
529 | { | |
530 | struct nouveau_bios *bios = (void *)object; | |
531 | return nouveau_subdev_fini(&bios->base, suspend); | |
532 | } | |
533 | ||
534 | struct nouveau_oclass | |
535 | nouveau_bios_oclass = { | |
536 | .handle = NV_SUBDEV(VBIOS, 0x00), | |
537 | .ofuncs = &(struct nouveau_ofuncs) { | |
538 | .ctor = nouveau_bios_ctor, | |
539 | .dtor = nouveau_bios_dtor, | |
540 | .init = nouveau_bios_init, | |
541 | .fini = nouveau_bios_fini, | |
542 | .rd08 = nouveau_bios_rd08, | |
543 | .rd16 = nouveau_bios_rd16, | |
544 | .rd32 = nouveau_bios_rd32, | |
545 | .wr08 = nouveau_bios_wr08, | |
546 | .wr16 = nouveau_bios_wr16, | |
547 | .wr32 = nouveau_bios_wr32, | |
548 | }, | |
549 | }; |