drm/nouveau/bios: split out shadow methods
authorBen Skeggs <bskeggs@redhat.com>
Wed, 17 Sep 2014 23:24:10 +0000 (09:24 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Tue, 2 Dec 2014 05:43:39 +0000 (15:43 +1000)
We're about to need to be able to fetch additional chunks of data beyond
the primary bios image, which makes fetching a lot more complicated.

This splits out the verious shadowing routines to be nothing more than
very dumb "fetch this much data from this offset" routines, and leaves
the logic of what and how much to fetch in common code.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/Makefile
drivers/gpu/drm/nouveau/core/include/subdev/bios/image.h [new file with mode: 0644]
drivers/gpu/drm/nouveau/core/subdev/bios/base.c
drivers/gpu/drm/nouveau/core/subdev/bios/image.c [new file with mode: 0644]
drivers/gpu/drm/nouveau/core/subdev/bios/priv.h [new file with mode: 0644]
drivers/gpu/drm/nouveau/core/subdev/bios/shadow.c [new file with mode: 0644]
drivers/gpu/drm/nouveau/core/subdev/bios/shadowacpi.c [new file with mode: 0644]
drivers/gpu/drm/nouveau/core/subdev/bios/shadowof.c [new file with mode: 0644]
drivers/gpu/drm/nouveau/core/subdev/bios/shadowpci.c [new file with mode: 0644]
drivers/gpu/drm/nouveau/core/subdev/bios/shadowramin.c [new file with mode: 0644]
drivers/gpu/drm/nouveau/core/subdev/bios/shadowrom.c [new file with mode: 0644]

index 12c24c8..f815ac0 100644 (file)
@@ -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 (file)
index 0000000..3348b45
--- /dev/null
@@ -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
index d45704a..7df3a27 100644 (file)
@@ -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 (file)
index 0000000..4b2120b
--- /dev/null
@@ -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 (file)
index 0000000..187d225
--- /dev/null
@@ -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 (file)
index 0000000..bb9e001
--- /dev/null
@@ -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 (file)
index 0000000..bc130c1
--- /dev/null
@@ -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 (file)
index 0000000..3abe487
--- /dev/null
@@ -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 (file)
index 0000000..1d0389c
--- /dev/null
@@ -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 (file)
index 0000000..5e58bba
--- /dev/null
@@ -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 (file)
index 0000000..b7992bc
--- /dev/null
@@ -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,
+};