Commit ad4a3626 authored by Ben Skeggs's avatar Ben Skeggs

drm/nouveau/bios: split out shadow methods

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: default avatarBen Skeggs <bskeggs@redhat.com>
parent e8972421
...@@ -41,12 +41,19 @@ nouveau-y += core/subdev/bios/extdev.o ...@@ -41,12 +41,19 @@ nouveau-y += core/subdev/bios/extdev.o
nouveau-y += core/subdev/bios/fan.o nouveau-y += core/subdev/bios/fan.o
nouveau-y += core/subdev/bios/gpio.o nouveau-y += core/subdev/bios/gpio.o
nouveau-y += core/subdev/bios/i2c.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/init.o
nouveau-y += core/subdev/bios/mxm.o nouveau-y += core/subdev/bios/mxm.o
nouveau-y += core/subdev/bios/perf.o nouveau-y += core/subdev/bios/perf.o
nouveau-y += core/subdev/bios/pll.o nouveau-y += core/subdev/bios/pll.o
nouveau-y += core/subdev/bios/ramcfg.o nouveau-y += core/subdev/bios/ramcfg.o
nouveau-y += core/subdev/bios/rammap.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/timing.o
nouveau-y += core/subdev/bios/therm.o nouveau-y += core/subdev/bios/therm.o
nouveau-y += core/subdev/bios/vmap.o nouveau-y += core/subdev/bios/vmap.o
......
#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
...@@ -31,6 +31,8 @@ ...@@ -31,6 +31,8 @@
#include <subdev/bios/bmp.h> #include <subdev/bios/bmp.h>
#include <subdev/bios/bit.h> #include <subdev/bios/bit.h>
#include "priv.h"
u8 u8
nvbios_checksum(const u8 *data, int size) nvbios_checksum(const u8 *data, int size)
{ {
...@@ -56,362 +58,21 @@ nvbios_findstr(const u8 *data, int size, const char *str, int len) ...@@ -56,362 +58,21 @@ nvbios_findstr(const u8 *data, int size, const char *str, int len)
return 0; return 0;
} }
#if defined(__powerpc__) int
static void nvbios_extend(struct nouveau_bios *bios, u32 length)
nouveau_bios_shadow_of(struct nouveau_bios *bios)
{ {
struct pci_dev *pdev = nv_device(bios)->pdev; if (bios->size < length) {
struct device_node *dn; u8 *prev = bios->data;
const u32 *data; if (!(bios->data = kmalloc(length, GFP_KERNEL))) {
int size; bios->data = prev;
return -ENOMEM;
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 ( (addr & 0x00000003) != 1) { memcpy(bios->data, prev, bios->size);
nv_debug(bios, "... not in vram\n"); bios->size = length;
return; kfree(prev);
} return 1;
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);
} }
return 0;
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;
} }
static u8 static u8
...@@ -472,7 +133,7 @@ nouveau_bios_ctor(struct nouveau_object *parent, ...@@ -472,7 +133,7 @@ nouveau_bios_ctor(struct nouveau_object *parent,
if (ret) if (ret)
return ret; return ret;
ret = nouveau_bios_shadow(bios); ret = nvbios_shadow(bios);
if (ret) if (ret)
return ret; return ret;
......
/*
* 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);
}
#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
/*
* 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;
}
/*
* 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,
};
/*
* 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
/*
* 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,
};
/*
* 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,
};
/*
* 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,
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment