diff options
-rw-r--r-- | drivers/gpu/drm/nouveau/Makefile | 7 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/include/subdev/bios/image.h | 13 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/bios/base.c | 369 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/bios/image.c | 55 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/bios/priv.h | 25 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/bios/shadow.c | 270 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/bios/shadowacpi.c | 111 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/bios/shadowof.c | 71 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/bios/shadowpci.c | 108 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/bios/shadowramin.c | 112 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/bios/shadowrom.c | 69 |
11 files changed, 856 insertions, 354 deletions
diff --git a/drivers/gpu/drm/nouveau/Makefile b/drivers/gpu/drm/nouveau/Makefile index 12c24c8abf7f..f815ac03664b 100644 --- a/drivers/gpu/drm/nouveau/Makefile +++ b/drivers/gpu/drm/nouveau/Makefile @@ -41,12 +41,19 @@ nouveau-y += core/subdev/bios/extdev.o nouveau-y += core/subdev/bios/fan.o nouveau-y += core/subdev/bios/gpio.o nouveau-y += core/subdev/bios/i2c.o +nouveau-y += core/subdev/bios/image.o nouveau-y += core/subdev/bios/init.o nouveau-y += core/subdev/bios/mxm.o nouveau-y += core/subdev/bios/perf.o nouveau-y += core/subdev/bios/pll.o nouveau-y += core/subdev/bios/ramcfg.o nouveau-y += core/subdev/bios/rammap.o +nouveau-y += core/subdev/bios/shadow.o +nouveau-y += core/subdev/bios/shadowacpi.o +nouveau-y += core/subdev/bios/shadowof.o +nouveau-y += core/subdev/bios/shadowpci.o +nouveau-y += core/subdev/bios/shadowramin.o +nouveau-y += core/subdev/bios/shadowrom.o nouveau-y += core/subdev/bios/timing.o nouveau-y += core/subdev/bios/therm.o nouveau-y += core/subdev/bios/vmap.o diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bios/image.h b/drivers/gpu/drm/nouveau/core/include/subdev/bios/image.h new file mode 100644 index 000000000000..3348b4580843 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bios/image.h @@ -0,0 +1,13 @@ +#ifndef __NVBIOS_IMAGE_H__ +#define __NVBIOS_IMAGE_H__ + +struct nvbios_image { + u32 base; + u32 size; + u8 type; + bool last; +}; + +bool nvbios_image(struct nouveau_bios *, int, struct nvbios_image *); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/base.c b/drivers/gpu/drm/nouveau/core/subdev/bios/base.c index d45704a2c2df..7df3a273553d 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/base.c @@ -31,6 +31,8 @@ #include <subdev/bios/bmp.h> #include <subdev/bios/bit.h> +#include "priv.h" + u8 nvbios_checksum(const u8 *data, int size) { @@ -56,362 +58,21 @@ nvbios_findstr(const u8 *data, int size, const char *str, int len) return 0; } -#if defined(__powerpc__) -static void -nouveau_bios_shadow_of(struct nouveau_bios *bios) +int +nvbios_extend(struct nouveau_bios *bios, u32 length) { - struct pci_dev *pdev = nv_device(bios)->pdev; - struct device_node *dn; - const u32 *data; - int size; - - dn = pci_device_to_OF_node(pdev); - if (!dn) { - nv_info(bios, "Unable to get the OF node\n"); - return; - } - - data = of_get_property(dn, "NVDA,BMP", &size); - if (data && size) { - bios->size = size; - bios->data = kmalloc(bios->size, GFP_KERNEL); - if (bios->data) - memcpy(bios->data, data, size); - } -} -#endif - -static void -nouveau_bios_shadow_pramin(struct nouveau_bios *bios) -{ - struct nouveau_device *device = nv_device(bios); - u64 addr = 0; - u32 bar0 = 0; - int i; - - if (device->card_type >= NV_50) { - if (device->card_type >= NV_C0 && device->card_type < GM100) { - if (nv_rd32(bios, 0x022500) & 0x00000001) - return; - } else - if (device->card_type >= GM100) { - if (nv_rd32(bios, 0x021c04) & 0x00000001) - return; - } - - addr = nv_rd32(bios, 0x619f04); - if (!(addr & 0x00000008)) { - nv_debug(bios, "... not enabled\n"); - return; + if (bios->size < length) { + u8 *prev = bios->data; + if (!(bios->data = kmalloc(length, GFP_KERNEL))) { + bios->data = prev; + return -ENOMEM; } - if ( (addr & 0x00000003) != 1) { - nv_debug(bios, "... not in vram\n"); - return; - } - - addr = (addr & 0xffffff00) << 8; - if (!addr) { - addr = (u64)nv_rd32(bios, 0x001700) << 16; - addr += 0xf0000; - } - - bar0 = nv_mask(bios, 0x001700, 0xffffffff, addr >> 16); - } - - /* bail if no rom signature */ - if (nv_rd08(bios, 0x700000) != 0x55 || - nv_rd08(bios, 0x700001) != 0xaa) - goto out; - - bios->size = nv_rd08(bios, 0x700002) * 512; - if (!bios->size) - goto out; - - bios->data = kmalloc(bios->size, GFP_KERNEL); - if (bios->data) { - for (i = 0; i < bios->size; i++) - nv_wo08(bios, i, nv_rd08(bios, 0x700000 + i)); - } - -out: - if (device->card_type >= NV_50) - nv_wr32(bios, 0x001700, bar0); -} - -static void -nouveau_bios_shadow_prom(struct nouveau_bios *bios) -{ - struct nouveau_device *device = nv_device(bios); - u32 pcireg, access; - u16 pcir; - int i; - - /* there is no prom on nv4x IGP's */ - if (device->card_type == NV_40 && device->chipset >= 0x4c) - return; - - /* enable access to rom */ - if (device->card_type >= NV_50) - pcireg = 0x088050; - else - pcireg = 0x001850; - access = nv_mask(bios, pcireg, 0x00000001, 0x00000000); - - /* WARNING: PROM accesses should always be 32-bits aligned. Other - * accesses work on most chipset but do not on Kepler chipsets - */ - - /* bail if no rom signature, with a workaround for a PROM reading - * issue on some chipsets. the first read after a period of - * inactivity returns the wrong result, so retry the first header - * byte a few times before giving up as a workaround - */ - i = 16; - do { - u32 data = le32_to_cpu(nv_rd32(bios, 0x300000)) & 0xffff; - if (data == 0xaa55) - break; - } while (i--); - - if (!i) - goto out; - - /* read entire bios image to system memory */ - bios->size = (le32_to_cpu(nv_rd32(bios, 0x300000)) >> 16) & 0xff; - bios->size = bios->size * 512; - if (!bios->size) - goto out; - - bios->data = kmalloc(bios->size, GFP_KERNEL); - if (!bios->data) - goto out; - - for (i = 0; i < bios->size; i += 4) - ((u32 *)bios->data)[i/4] = nv_rd32(bios, 0x300000 + i); - - /* check the PCI record header */ - pcir = nv_ro16(bios, 0x0018); - if (bios->data[pcir + 0] != 'P' || - bios->data[pcir + 1] != 'C' || - bios->data[pcir + 2] != 'I' || - bios->data[pcir + 3] != 'R') { - bios->size = 0; - kfree(bios->data); - } - -out: - /* disable access to rom */ - nv_wr32(bios, pcireg, access); -} - -#if defined(CONFIG_ACPI) && defined(CONFIG_X86) -int nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len); -bool nouveau_acpi_rom_supported(struct pci_dev *pdev); -#else -static inline bool -nouveau_acpi_rom_supported(struct pci_dev *pdev) { - return false; -} - -static inline int -nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len) { - return -EINVAL; -} -#endif - -static void -nouveau_bios_shadow_acpi(struct nouveau_bios *bios) -{ - struct pci_dev *pdev = nv_device(bios)->pdev; - int ret, cnt, i; - - if (!nouveau_acpi_rom_supported(pdev)) { - bios->data = NULL; - return; - } - - bios->size = 0; - bios->data = kmalloc(4096, GFP_KERNEL); - if (bios->data) { - if (nouveau_acpi_get_bios_chunk(bios->data, 0, 4096) == 4096) - bios->size = bios->data[2] * 512; - kfree(bios->data); + memcpy(bios->data, prev, bios->size); + bios->size = length; + kfree(prev); + return 1; } - - if (!bios->size) - return; - - bios->data = kmalloc(bios->size, GFP_KERNEL); - if (bios->data) { - /* disobey the acpi spec - much faster on at least w530 ... */ - ret = nouveau_acpi_get_bios_chunk(bios->data, 0, bios->size); - if (ret != bios->size || - nvbios_checksum(bios->data, bios->size)) { - /* ... that didn't work, ok, i'll be good now */ - for (i = 0; i < bios->size; i += cnt) { - cnt = min((bios->size - i), (u32)4096); - ret = nouveau_acpi_get_bios_chunk(bios->data, i, cnt); - if (ret != cnt) - break; - } - } - } -} - -static void -nouveau_bios_shadow_pci(struct nouveau_bios *bios) -{ - struct pci_dev *pdev = nv_device(bios)->pdev; - size_t size; - - if (!pci_enable_rom(pdev)) { - void __iomem *rom = pci_map_rom(pdev, &size); - if (rom && size) { - bios->data = kmalloc(size, GFP_KERNEL); - if (bios->data) { - memcpy_fromio(bios->data, rom, size); - bios->size = size; - } - } - if (rom) - pci_unmap_rom(pdev, rom); - - pci_disable_rom(pdev); - } -} - -static void -nouveau_bios_shadow_platform(struct nouveau_bios *bios) -{ - struct pci_dev *pdev = nv_device(bios)->pdev; - size_t size; - - void __iomem *rom = pci_platform_rom(pdev, &size); - if (rom && size) { - bios->data = kmalloc(size, GFP_KERNEL); - if (bios->data) { - memcpy_fromio(bios->data, rom, size); - bios->size = size; - } - } -} - -static int -nouveau_bios_score(struct nouveau_bios *bios, const bool writeable) -{ - if (bios->size < 3 || !bios->data || bios->data[0] != 0x55 || - bios->data[1] != 0xAA) { - nv_info(bios, "... signature not found\n"); - return 0; - } - - if (nvbios_checksum(bios->data, - min_t(u32, bios->data[2] * 512, bios->size))) { - nv_info(bios, "... checksum invalid\n"); - /* if a ro image is somewhat bad, it's probably all rubbish */ - return writeable ? 2 : 1; - } - - nv_info(bios, "... appears to be valid\n"); - return 3; -} - -struct methods { - const char desc[16]; - void (*shadow)(struct nouveau_bios *); - const bool rw; - int score; - u32 size; - u8 *data; -}; - -static int -nouveau_bios_shadow(struct nouveau_bios *bios) -{ - struct methods shadow_methods[] = { -#if defined(__powerpc__) - { "OpenFirmware", nouveau_bios_shadow_of, true, 0, 0, NULL }, -#endif - { "PRAMIN", nouveau_bios_shadow_pramin, true, 0, 0, NULL }, - { "PROM", nouveau_bios_shadow_prom, false, 0, 0, NULL }, - { "ACPI", nouveau_bios_shadow_acpi, true, 0, 0, NULL }, - { "PCIROM", nouveau_bios_shadow_pci, true, 0, 0, NULL }, - { "PLATFORM", nouveau_bios_shadow_platform, true, 0, 0, NULL }, - {} - }; - struct methods *mthd, *best; - const struct firmware *fw; - const char *optarg; - int optlen, ret; - char *source; - - optarg = nouveau_stropt(nv_device(bios)->cfgopt, "NvBios", &optlen); - source = optarg ? kstrndup(optarg, optlen, GFP_KERNEL) : NULL; - if (source) { - /* try to match one of the built-in methods */ - mthd = shadow_methods; - do { - if (strcasecmp(source, mthd->desc)) - continue; - nv_info(bios, "source: %s\n", mthd->desc); - - mthd->shadow(bios); - mthd->score = nouveau_bios_score(bios, mthd->rw); - if (mthd->score) { - kfree(source); - return 0; - } - } while ((++mthd)->shadow); - - /* attempt to load firmware image */ - ret = request_firmware(&fw, source, &nv_device(bios)->pdev->dev); - if (ret == 0) { - bios->size = fw->size; - bios->data = kmemdup(fw->data, fw->size, GFP_KERNEL); - release_firmware(fw); - - nv_info(bios, "image: %s\n", source); - if (nouveau_bios_score(bios, 1)) { - kfree(source); - return 0; - } - - kfree(bios->data); - bios->data = NULL; - } - - nv_error(bios, "source \'%s\' invalid\n", source); - kfree(source); - } - - mthd = shadow_methods; - do { - nv_info(bios, "checking %s for image...\n", mthd->desc); - mthd->shadow(bios); - mthd->score = nouveau_bios_score(bios, mthd->rw); - mthd->size = bios->size; - mthd->data = bios->data; - bios->data = NULL; - } while (mthd->score != 3 && (++mthd)->shadow); - - mthd = shadow_methods; - best = mthd; - do { - if (mthd->score > best->score) { - kfree(best->data); - best = mthd; - } - } while ((++mthd)->shadow); - - if (best->score) { - nv_info(bios, "using image from %s\n", best->desc); - bios->size = best->size; - bios->data = best->data; - return 0; - } - - nv_error(bios, "unable to locate usable image\n"); - return -EINVAL; + return 0; } static u8 @@ -472,7 +133,7 @@ nouveau_bios_ctor(struct nouveau_object *parent, if (ret) return ret; - ret = nouveau_bios_shadow(bios); + ret = nvbios_shadow(bios); if (ret) return ret; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/image.c b/drivers/gpu/drm/nouveau/core/subdev/bios/image.c new file mode 100644 index 000000000000..4b2120b1fab1 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/image.c @@ -0,0 +1,55 @@ +/* + * Copyright 2014 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs <bskeggs@redhat.com> + */ + +#include <subdev/bios.h> +#include <subdev/bios/image.h> + +static bool +nvbios_imagen(struct nouveau_bios *bios, struct nvbios_image *image) +{ + u32 data; + + switch ((data = nv_ro16(bios, image->base + 0x00))) { + case 0xaa55: + break; + default: + nv_debug(bios, "%08x: ROM signature (%04x) unknown\n", + image->base, data); + return false; + } + + image->size = nv_ro08(bios, image->base + 0x02) * 512; + image->type = 0x00; + image->last = true; + return true; +} + +bool +nvbios_image(struct nouveau_bios *bios, int idx, struct nvbios_image *image) +{ + memset(image, 0x00, sizeof(*image)); + if (idx) + return false; + return nvbios_imagen(bios, image); +} diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/priv.h b/drivers/gpu/drm/nouveau/core/subdev/bios/priv.h new file mode 100644 index 000000000000..187d225bd1e9 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/priv.h @@ -0,0 +1,25 @@ +#ifndef __NVKM_BIOS_PRIV_H__ +#define __NVKM_BIOS_PRIV_H__ + +#include <subdev/bios.h> + +struct nvbios_source { + const char *name; + void *(*init)(struct nouveau_bios *, const char *); + void (*fini)(void *); + u32 (*read)(void *, u32 offset, u32 length, struct nouveau_bios *); + bool rw; +}; + +int nvbios_extend(struct nouveau_bios *, u32 length); +int nvbios_shadow(struct nouveau_bios *); + +extern const struct nvbios_source nvbios_rom; +extern const struct nvbios_source nvbios_ramin; +extern const struct nvbios_source nvbios_acpi_fast; +extern const struct nvbios_source nvbios_acpi_slow; +extern const struct nvbios_source nvbios_pcirom; +extern const struct nvbios_source nvbios_platform; +extern const struct nvbios_source nvbios_of; + +#endif diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/shadow.c b/drivers/gpu/drm/nouveau/core/subdev/bios/shadow.c new file mode 100644 index 000000000000..bb9e0018d936 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/shadow.c @@ -0,0 +1,270 @@ +/* + * Copyright 2014 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs <bskeggs@redhat.com> + */ + +#include "priv.h" +#include <core/option.h> +#include <subdev/bios/image.h> + +struct shadow { + struct nouveau_oclass base; + u32 skip; + const struct nvbios_source *func; + void *data; + u32 size; + int score; +}; + +static bool +shadow_fetch(struct nouveau_bios *bios, u32 upto) +{ + struct shadow *mthd = (void *)nv_object(bios)->oclass; + const u32 limit = (upto + 3) & ~3; + const u32 start = bios->size; + void *data = mthd->data; + if (nvbios_extend(bios, limit) > 0) { + u32 read = mthd->func->read(data, start, limit - start, bios); + bios->size = start + read; + } + return bios->size >= limit; +} + +static u8 +shadow_rd08(struct nouveau_object *object, u64 addr) +{ + struct nouveau_bios *bios = (void *)object; + if (shadow_fetch(bios, addr + 1)) + return bios->data[addr]; + return 0x00; +} + +static u16 +shadow_rd16(struct nouveau_object *object, u64 addr) +{ + struct nouveau_bios *bios = (void *)object; + if (shadow_fetch(bios, addr + 2)) + return get_unaligned_le16(&bios->data[addr]); + return 0x0000; +} + +static u32 +shadow_rd32(struct nouveau_object *object, u64 addr) +{ + struct nouveau_bios *bios = (void *)object; + if (shadow_fetch(bios, addr + 4)) + return get_unaligned_le32(&bios->data[addr]); + return 0x00000000; +} + +static struct nouveau_oclass +shadow_class = { + .handle = NV_SUBDEV(VBIOS, 0x00), + .ofuncs = &(struct nouveau_ofuncs) { + .rd08 = shadow_rd08, + .rd16 = shadow_rd16, + .rd32 = shadow_rd32, + }, +}; + +static int +shadow_image(struct nouveau_bios *bios, int idx, struct shadow *mthd) +{ + struct nvbios_image image; + int score = 1; + + if (!nvbios_image(bios, idx, &image)) { + nv_debug(bios, "image %d invalid\n", idx); + return 0; + } + nv_debug(bios, "%08x: type %02x, %d bytes\n", + image.base, image.type, image.size); + + if (!shadow_fetch(bios, image.size)) { + nv_debug(bios, "%08x: fetch failed\n", image.base); + return 0; + } + + switch (image.type) { + case 0x00: + if (nvbios_checksum(&bios->data[image.base], image.size)) { + nv_debug(bios, "%08x: checksum failed\n", image.base); + if (mthd->func->rw) + score += 1; + score += 1; + } else { + score += 3; + } + break; + default: + score += 3; + break; + } + + if (!image.last) + score += shadow_image(bios, idx + 1, mthd); + return score; +} + +static int +shadow_score(struct nouveau_bios *bios, struct shadow *mthd) +{ + struct nouveau_oclass *oclass = nv_object(bios)->oclass; + int score; + nv_object(bios)->oclass = &mthd->base; + score = shadow_image(bios, 0, mthd); + nv_object(bios)->oclass = oclass; + return score; + +} + +static int +shadow_method(struct nouveau_bios *bios, struct shadow *mthd, const char *name) +{ + const struct nvbios_source *func = mthd->func; + if (func->name) { + nv_debug(bios, "trying %s...\n", name ? name : func->name); + if (func->init) { + mthd->data = func->init(bios, name); + if (IS_ERR(mthd->data)) { + mthd->data = NULL; + return 0; + } + } + mthd->score = shadow_score(bios, mthd); + if (func->fini) + func->fini(mthd->data); + nv_debug(bios, "scored %d\n", mthd->score); + mthd->data = bios->data; + mthd->size = bios->size; + bios->data = NULL; + bios->size = 0; + } + return mthd->score; +} + +static u32 +shadow_fw_read(void *data, u32 offset, u32 length, struct nouveau_bios *bios) +{ + const struct firmware *fw = data; + if (offset + length <= fw->size) { + memcpy(bios->data + offset, fw->data + offset, length); + return length; + } + return 0; +} + +static void * +shadow_fw_init(struct nouveau_bios *bios, const char *name) +{ + struct device *dev = &nv_device(bios)->pdev->dev; + const struct firmware *fw; + int ret = request_firmware(&fw, name, dev); + if (ret) + return ERR_PTR(-ENOENT); + return (void *)fw; +} + +static const struct nvbios_source +shadow_fw = { + .name = "firmware", + .init = shadow_fw_init, + .fini = (void(*)(void *))release_firmware, + .read = shadow_fw_read, + .rw = false, +}; + +int +nvbios_shadow(struct nouveau_bios *bios) +{ + struct shadow mthds[] = { + { shadow_class, 0, &nvbios_of }, + { shadow_class, 0, &nvbios_ramin }, + { shadow_class, 0, &nvbios_rom }, + { shadow_class, 0, &nvbios_acpi_fast }, + { shadow_class, 4, &nvbios_acpi_slow }, + { shadow_class, 1, &nvbios_pcirom }, + { shadow_class, 1, &nvbios_platform }, + { shadow_class } + }, *mthd = mthds, *best = NULL; + const char *optarg; + char *source; + int optlen; + + /* handle user-specified bios source */ + optarg = nouveau_stropt(nv_device(bios)->cfgopt, "NvBios", &optlen); + source = optarg ? kstrndup(optarg, optlen, GFP_KERNEL) : NULL; + if (source) { + /* try to match one of the built-in methods */ + for (mthd = mthds; mthd->func; mthd++) { + if (mthd->func->name && + !strcasecmp(source, mthd->func->name)) { + best = mthd; + if (shadow_method(bios, mthd, NULL)) + break; + } + } + + /* otherwise, attempt to load as firmware */ + if (!best && (best = mthd)) { + mthd->func = &shadow_fw; + shadow_method(bios, mthd, source); + mthd->func = NULL; + } + + if (!best->score) { + nv_error(bios, "%s invalid\n", source); + kfree(source); + source = NULL; + } + } + + /* scan all potential bios sources, looking for best image */ + if (!best || !best->score) { + for (mthd = mthds, best = mthd; mthd->func; mthd++) { + if (!mthd->skip || best->score < mthd->skip) { + if (shadow_method(bios, mthd, NULL)) { + if (mthd->score > best->score) + best = mthd; + } + } + } + } + + /* cleanup the ones we didn't use */ + for (mthd = mthds; mthd->func; mthd++) { + if (mthd != best) + kfree(mthd->data); + } + + if (!best->score) { + nv_fatal(bios, "unable to locate usable image\n"); + return -EINVAL; + } + + nv_info(bios, "using image from %s\n", best->func ? + best->func->name : source); + bios->data = best->data; + bios->size = best->size; + kfree(source); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/shadowacpi.c b/drivers/gpu/drm/nouveau/core/subdev/bios/shadowacpi.c new file mode 100644 index 000000000000..bc130c12ec06 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/shadowacpi.c @@ -0,0 +1,111 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "priv.h" + +#if defined(CONFIG_ACPI) && defined(CONFIG_X86) +int nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len); +bool nouveau_acpi_rom_supported(struct pci_dev *pdev); +#else +static inline bool +nouveau_acpi_rom_supported(struct pci_dev *pdev) +{ + return false; +} + +static inline int +nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len) +{ + return -EINVAL; +} +#endif + +/* This version of the shadow function disobeys the ACPI spec and tries + * to fetch in units of more than 4KiB at a time. This is a LOT faster + * on some systems, such as Lenovo W530. + */ +static u32 +acpi_read_fast(void *data, u32 offset, u32 length, struct nouveau_bios *bios) +{ + u32 limit = (offset + length + 0xfff) & ~0xfff; + u32 start = offset & ~0x00000fff; + u32 fetch = limit - start; + + if (nvbios_extend(bios, limit) > 0) { + int ret = nouveau_acpi_get_bios_chunk(bios->data, start, fetch); + if (ret == fetch) + return fetch; + } + + return 0; +} + +/* Other systems, such as the one in fdo#55948, will report a success + * but only return 4KiB of data. The common bios fetching logic will + * detect an invalid image, and fall back to this version of the read + * function. + */ +static u32 +acpi_read_slow(void *data, u32 offset, u32 length, struct nouveau_bios *bios) +{ + u32 limit = (offset + length + 0xfff) & ~0xfff; + u32 start = offset & ~0xfff; + u32 fetch = 0; + + if (nvbios_extend(bios, limit) > 0) { + while (start + fetch < limit) { + int ret = nouveau_acpi_get_bios_chunk(bios->data, + start + fetch, + 0x1000); + if (ret != 0x1000) + break; + fetch += 0x1000; + } + } + + return fetch; +} + +static void * +acpi_init(struct nouveau_bios *bios, const char *name) +{ + if (!nouveau_acpi_rom_supported(nv_device(bios)->pdev)) + return ERR_PTR(-ENODEV); + return NULL; +} + +const struct nvbios_source +nvbios_acpi_fast = { + .name = "ACPI", + .init = acpi_init, + .read = acpi_read_fast, + .rw = false, +}; + +const struct nvbios_source +nvbios_acpi_slow = { + .name = "ACPI", + .init = acpi_init, + .read = acpi_read_slow, + .rw = false, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/shadowof.c b/drivers/gpu/drm/nouveau/core/subdev/bios/shadowof.c new file mode 100644 index 000000000000..3abe487a6025 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/shadowof.c @@ -0,0 +1,71 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "priv.h" + +#if defined(__powerpc__) +struct priv { + const void __iomem *data; + int size; +}; + +static u32 +of_read(void *data, u32 offset, u32 length, struct nouveau_bios *bios) +{ + struct priv *priv = data; + if (offset + length <= priv->size) { + memcpy_fromio(bios->data + offset, priv->data + offset, length); + return length; + } + return 0; +} + +static void * +of_init(struct nouveau_bios *bios, const char *name) +{ + struct pci_dev *pdev = nv_device(bios)->pdev; + struct device_node *dn; + struct priv *priv; + if (!(dn = pci_device_to_OF_node(pdev))) + return ERR_PTR(-ENODEV); + if (!(priv = kzalloc(sizeof(*priv), GFP_KERNEL))) + return ERR_PTR(-ENOMEM); + if ((priv->data = of_get_property(dn, "NVDA,BMP", &priv->size))) + return priv; + kfree(priv); + return ERR_PTR(-EINVAL); +} + +const struct nvbios_source +nvbios_of = { + .name = "OpenFirmware", + .init = of_init, + .fini = (void(*)(void *))kfree, + .read = of_read, + .rw = false, +}; +#else +const struct nvbios_source +nvbios_of = { +}; +#endif diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/shadowpci.c b/drivers/gpu/drm/nouveau/core/subdev/bios/shadowpci.c new file mode 100644 index 000000000000..1d0389c0abef --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/shadowpci.c @@ -0,0 +1,108 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "priv.h" + +struct priv { + struct pci_dev *pdev; + void __iomem *rom; + size_t size; +}; + +static u32 +pcirom_read(void *data, u32 offset, u32 length, struct nouveau_bios *bios) +{ + struct priv *priv = data; + if (offset + length <= priv->size) { + memcpy_fromio(bios->data + offset, priv->rom + offset, length); + return length; + } + return 0; +} + +static void +pcirom_fini(void *data) +{ + struct priv *priv = data; + pci_unmap_rom(priv->pdev, priv->rom); + pci_disable_rom(priv->pdev); + kfree(priv); +} + +static void * +pcirom_init(struct nouveau_bios *bios, const char *name) +{ + struct pci_dev *pdev = nv_device(bios)->pdev; + struct priv *priv = NULL; + int ret; + + if (!(ret = pci_enable_rom(pdev))) { + if (ret = -ENOMEM, + (priv = kmalloc(sizeof(*priv), GFP_KERNEL))) { + if (ret = -EFAULT, + (priv->rom = pci_map_rom(pdev, &priv->size))) { + priv->pdev = pdev; + return priv; + } + kfree(priv); + } + pci_disable_rom(pdev); + } + + return ERR_PTR(ret); +} + +const struct nvbios_source +nvbios_pcirom = { + .name = "PCIROM", + .init = pcirom_init, + .fini = pcirom_fini, + .read = pcirom_read, + .rw = true, +}; + +static void * +platform_init(struct nouveau_bios *bios, const char *name) +{ + struct pci_dev *pdev = nv_device(bios)->pdev; + struct priv *priv; + int ret = -ENOMEM; + + if ((priv = kmalloc(sizeof(*priv), GFP_KERNEL))) { + if (ret = -ENODEV, + (priv->rom = pci_platform_rom(pdev, &priv->size))) + return priv; + kfree(priv); + } + + return ERR_PTR(ret); +} + +const struct nvbios_source +nvbios_platform = { + .name = "PLATFORM", + .init = platform_init, + .fini = (void(*)(void *))kfree, + .read = pcirom_read, + .rw = true, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/shadowramin.c b/drivers/gpu/drm/nouveau/core/subdev/bios/shadowramin.c new file mode 100644 index 000000000000..5e58bba0dd5c --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/shadowramin.c @@ -0,0 +1,112 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "priv.h" + +struct priv { + struct nouveau_bios *bios; + u32 bar0; +}; + +static u32 +pramin_read(void *data, u32 offset, u32 length, struct nouveau_bios *bios) +{ + u32 i; + if (offset + length <= 0x00100000) { + for (i = offset; i < offset + length; i += 4) + *(u32 *)&bios->data[i] = nv_rd32(bios, 0x700000 + i); + return length; + } + return 0; +} + +static void +pramin_fini(void *data) +{ + struct priv *priv = data; + nv_wr32(priv->bios, 0x001700, priv->bar0); + kfree(priv); +} + +static void * +pramin_init(struct nouveau_bios *bios, const char *name) +{ + struct priv *priv = NULL; + u64 addr = 0; + + /* PRAMIN always potentially available prior to nv50 */ + if (nv_device(bios)->card_type < NV_50) + return NULL; + + /* we can't get the bios image pointer without PDISP */ + if (nv_device(bios)->card_type >= GM100) + addr = nv_rd32(bios, 0x021c04); + else + if (nv_device(bios)->card_type >= NV_C0) + addr = nv_rd32(bios, 0x022500); + if (addr & 0x00000001) { + nv_debug(bios, "... display disabled\n"); + return ERR_PTR(-ENODEV); + } + + /* check that the window is enabled and in vram, particularly + * important as we don't want to be touching vram on an + * uninitialised board + */ + addr = nv_rd32(bios, 0x619f04); + if (!(addr & 0x00000008)) { + nv_debug(bios, "... not enabled\n"); + return ERR_PTR(-ENODEV); + } + if ( (addr & 0x00000003) != 1) { + nv_debug(bios, "... not in vram\n"); + return ERR_PTR(-ENODEV); + } + + /* some alternate method inherited from xf86-video-nv... */ + addr = (addr & 0xffffff00) << 8; + if (!addr) { + addr = (u64)nv_rd32(bios, 0x001700) << 16; + addr += 0xf0000; + } + + /* modify bar0 PRAMIN window to cover the bios image */ + if (!(priv = kmalloc(sizeof(*priv), GFP_KERNEL))) { + nv_error(bios, "... out of memory\n"); + return ERR_PTR(-ENOMEM); + } + + priv->bios = bios; + priv->bar0 = nv_rd32(bios, 0x001700); + nv_wr32(bios, 0x001700, addr >> 16); + return priv; +} + +const struct nvbios_source +nvbios_ramin = { + .name = "PRAMIN", + .init = pramin_init, + .fini = pramin_fini, + .read = pramin_read, + .rw = true, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/shadowrom.c b/drivers/gpu/drm/nouveau/core/subdev/bios/shadowrom.c new file mode 100644 index 000000000000..b7992bc3ffa5 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/shadowrom.c @@ -0,0 +1,69 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "priv.h" + +static u32 +prom_read(void *data, u32 offset, u32 length, struct nouveau_bios *bios) +{ + u32 i; + if (offset + length <= 0x00100000) { + for (i = offset; i < offset + length; i += 4) + *(u32 *)&bios->data[i] = nv_rd32(bios, 0x300000 + i); + return length; + } + return 0; +} + +static void +prom_fini(void *data) +{ + struct nouveau_bios *bios = data; + if (nv_device(bios)->card_type < NV_50) + nv_mask(bios, 0x001850, 0x00000001, 0x00000001); + else + nv_mask(bios, 0x088050, 0x00000001, 0x00000001); +} + +static void * +prom_init(struct nouveau_bios *bios, const char *name) +{ + if (nv_device(bios)->card_type < NV_50) { + if (nv_device(bios)->card_type == NV_40 && + nv_device(bios)->chipset >= 0x4c) + return ERR_PTR(-ENODEV); + nv_mask(bios, 0x001850, 0x00000001, 0x00000000); + } else { + nv_mask(bios, 0x088050, 0x00000001, 0x00000000); + } + return bios; +} + +const struct nvbios_source +nvbios_rom = { + .name = "PROM", + .init = prom_init, + .fini = prom_fini, + .read = prom_read, + .rw = false, +}; |