提交 4489b983 编写于 作者: B Ben Skeggs

drm/nouveau/bios: rework vbios shadowing

Refactored to allow shadowing of VBIOS images longer than 64KiB, which
allows us to pass the VBIOS checksum test on certain boards.

There's also a workaround for reading the PROM VBIOS on some chipsets.
Signed-off-by: NBen Skeggs <bskeggs@redhat.com>
上级 05a7c15d
...@@ -65,195 +65,232 @@ static bool nv_cksum(const uint8_t *data, unsigned int length) ...@@ -65,195 +65,232 @@ static bool nv_cksum(const uint8_t *data, unsigned int length)
} }
static int static int
score_vbios(struct drm_device *dev, const uint8_t *data, const bool writeable) score_vbios(struct nvbios *bios, const bool writeable)
{ {
if (!(data[0] == 0x55 && data[1] == 0xAA)) { if (!bios->data || bios->data[0] != 0x55 || bios->data[1] != 0xAA) {
NV_TRACEWARN(dev, "... BIOS signature not found\n"); NV_TRACEWARN(bios->dev, "... BIOS signature not found\n");
return 0; return 0;
} }
if (nv_cksum(data, data[2] * 512)) { if (nv_cksum(bios->data, bios->data[2] * 512)) {
NV_TRACEWARN(dev, "... BIOS checksum invalid\n"); NV_TRACEWARN(bios->dev, "... BIOS checksum invalid\n");
/* if a ro image is somewhat bad, it's probably all rubbish */ /* if a ro image is somewhat bad, it's probably all rubbish */
return writeable ? 2 : 1; return writeable ? 2 : 1;
} else }
NV_TRACE(dev, "... appears to be valid\n");
NV_TRACE(bios->dev, "... appears to be valid\n");
return 3; return 3;
} }
static void load_vbios_prom(struct drm_device *dev, uint8_t *data) static void
bios_shadow_prom(struct nvbios *bios)
{ {
struct drm_device *dev = bios->dev;
struct drm_nouveau_private *dev_priv = dev->dev_private; struct drm_nouveau_private *dev_priv = dev->dev_private;
uint32_t pci_nv_20, save_pci_nv_20; u32 pcireg, access;
int pcir_ptr; u16 pcir;
int i; int i;
/* enable access to rom */
if (dev_priv->card_type >= NV_50) if (dev_priv->card_type >= NV_50)
pci_nv_20 = 0x88050; pcireg = 0x088050;
else else
pci_nv_20 = NV_PBUS_PCI_NV_20; pcireg = NV_PBUS_PCI_NV_20;
access = nv_mask(dev, pcireg, 0x00000001, 0x00000000);
/* enable ROM access */ /* bail if no rom signature, with a workaround for a PROM reading
save_pci_nv_20 = nvReadMC(dev, pci_nv_20); * issue on some chipsets. the first read after a period of
nvWriteMC(dev, pci_nv_20, * inactivity returns the wrong result, so retry the first header
save_pci_nv_20 & ~NV_PBUS_PCI_NV_20_ROM_SHADOW_ENABLED); * byte a few times before giving up as a workaround
*/
i = 16;
do {
if (nv_rd08(dev, NV_PROM_OFFSET + 0) == 0x55)
break;
} while (i--);
/* bail if no rom signature */ if (!i || nv_rd08(dev, NV_PROM_OFFSET + 1) != 0xaa)
if (nv_rd08(dev, NV_PROM_OFFSET) != 0x55 ||
nv_rd08(dev, NV_PROM_OFFSET + 1) != 0xaa)
goto out; goto out;
/* additional check (see note below) - read PCI record header */ /* additional check (see note below) - read PCI record header */
pcir_ptr = nv_rd08(dev, NV_PROM_OFFSET + 0x18) | pcir = nv_rd08(dev, NV_PROM_OFFSET + 0x18) |
nv_rd08(dev, NV_PROM_OFFSET + 0x19) << 8; nv_rd08(dev, NV_PROM_OFFSET + 0x19) << 8;
if (nv_rd08(dev, NV_PROM_OFFSET + pcir_ptr) != 'P' || if (nv_rd08(dev, NV_PROM_OFFSET + pcir + 0) != 'P' ||
nv_rd08(dev, NV_PROM_OFFSET + pcir_ptr + 1) != 'C' || nv_rd08(dev, NV_PROM_OFFSET + pcir + 1) != 'C' ||
nv_rd08(dev, NV_PROM_OFFSET + pcir_ptr + 2) != 'I' || nv_rd08(dev, NV_PROM_OFFSET + pcir + 2) != 'I' ||
nv_rd08(dev, NV_PROM_OFFSET + pcir_ptr + 3) != 'R') nv_rd08(dev, NV_PROM_OFFSET + pcir + 3) != 'R')
goto out; goto out;
/* on some 6600GT/6800LE prom reads are messed up. nvclock alleges a /* read entire bios image to system memory */
* a good read may be obtained by waiting or re-reading (cargocult: 5x) bios->length = nv_rd08(dev, NV_PROM_OFFSET + 2) * 512;
* each byte. we'll hope pramin has something usable instead bios->data = kmalloc(bios->length, GFP_KERNEL);
*/ if (bios->data) {
for (i = 0; i < NV_PROM_SIZE; i++) for (i = 0; i < bios->length; i++)
data[i] = nv_rd08(dev, NV_PROM_OFFSET + i); bios->data[i] = nv_rd08(dev, NV_PROM_OFFSET + i);
}
out: out:
/* disable ROM access */ /* disable access to rom */
nvWriteMC(dev, pci_nv_20, nv_wr32(dev, pcireg, access);
save_pci_nv_20 | NV_PBUS_PCI_NV_20_ROM_SHADOW_ENABLED);
} }
static void load_vbios_pramin(struct drm_device *dev, uint8_t *data) static void
bios_shadow_pramin(struct nvbios *bios)
{ {
struct drm_device *dev = bios->dev;
struct drm_nouveau_private *dev_priv = dev->dev_private; struct drm_nouveau_private *dev_priv = dev->dev_private;
uint32_t old_bar0_pramin = 0; u32 bar0 = 0;
int i; int i;
if (dev_priv->card_type >= NV_50) { if (dev_priv->card_type >= NV_50) {
u64 addr = (u64)(nv_rd32(dev, 0x619f04) & 0xffffff00) << 8; u64 addr = (u64)(nv_rd32(dev, 0x619f04) & 0xffffff00) << 8;
if (!addr) { if (!addr) {
addr = (u64)nv_rd32(dev, 0x1700) << 16; addr = (u64)nv_rd32(dev, 0x001700) << 16;
addr += 0xf0000; addr += 0xf0000;
} }
old_bar0_pramin = nv_rd32(dev, 0x1700); bar0 = nv_mask(dev, 0x001700, 0xffffffff, addr >> 16);
nv_wr32(dev, 0x1700, addr >> 16);
} }
/* bail if no rom signature */ /* bail if no rom signature */
if (nv_rd08(dev, NV_PRAMIN_OFFSET) != 0x55 || if (nv_rd08(dev, NV_PRAMIN_OFFSET + 0) != 0x55 ||
nv_rd08(dev, NV_PRAMIN_OFFSET + 1) != 0xaa) nv_rd08(dev, NV_PRAMIN_OFFSET + 1) != 0xaa)
goto out; goto out;
for (i = 0; i < NV_PROM_SIZE; i++) bios->length = nv_rd08(dev, NV_PRAMIN_OFFSET + 2) * 512;
data[i] = nv_rd08(dev, NV_PRAMIN_OFFSET + i); bios->data = kmalloc(bios->length, GFP_KERNEL);
if (bios->data) {
for (i = 0; i < bios->length; i++)
bios->data[i] = nv_rd08(dev, NV_PRAMIN_OFFSET + i);
}
out: out:
if (dev_priv->card_type >= NV_50) if (dev_priv->card_type >= NV_50)
nv_wr32(dev, 0x1700, old_bar0_pramin); nv_wr32(dev, 0x001700, bar0);
} }
static void load_vbios_pci(struct drm_device *dev, uint8_t *data) static void
bios_shadow_pci(struct nvbios *bios)
{ {
void __iomem *rom = NULL; struct pci_dev *pdev = bios->dev->pdev;
size_t rom_len; size_t length;
int ret;
ret = pci_enable_rom(dev->pdev); if (!pci_enable_rom(pdev)) {
if (ret) void __iomem *rom = pci_map_rom(pdev, &length);
return; if (rom) {
bios->data = kmalloc(length, GFP_KERNEL);
rom = pci_map_rom(dev->pdev, &rom_len); if (bios->data) {
if (!rom) memcpy_fromio(bios->data, rom, length);
goto out; bios->length = length;
memcpy_fromio(data, rom, rom_len); }
pci_unmap_rom(dev->pdev, rom); pci_unmap_rom(pdev, rom);
}
out: pci_disable_rom(pdev);
pci_disable_rom(dev->pdev); }
} }
static void load_vbios_acpi(struct drm_device *dev, uint8_t *data) static void
bios_shadow_acpi(struct nvbios *bios)
{ {
int i; struct pci_dev *pdev = bios->dev->pdev;
int ret; int ptr, len, ret;
int size = 64 * 1024; u8 data[3];
if (!nouveau_acpi_rom_supported(dev->pdev)) if (!nouveau_acpi_rom_supported(pdev))
return; return;
for (i = 0; i < (size / ROM_BIOS_PAGE); i++) { ret = nouveau_acpi_get_bios_chunk(data, 0, sizeof(data));
ret = nouveau_acpi_get_bios_chunk(data, if (ret != sizeof(data))
(i * ROM_BIOS_PAGE), return;
ROM_BIOS_PAGE);
if (ret <= 0) bios->length = min(data[2] * 512, 65536);
break; bios->data = kmalloc(bios->length, GFP_KERNEL);
} if (!bios->data)
return;
len = bios->length;
ptr = 0;
while (len) {
int size = (len > ROM_BIOS_PAGE) ? ROM_BIOS_PAGE : len;
ret = nouveau_acpi_get_bios_chunk(bios->data, ptr, size);
if (ret != size) {
kfree(bios->data);
bios->data = NULL;
return; return;
}
len -= size;
ptr += size;
}
} }
struct methods { struct methods {
const char desc[8]; const char desc[8];
void (*loadbios)(struct drm_device *, uint8_t *); void (*shadow)(struct nvbios *);
const bool rw; const bool rw;
int score;
u32 size;
u8 *data;
}; };
static struct methods shadow_methods[] = { static bool
{ "PRAMIN", load_vbios_pramin, true }, bios_shadow(struct drm_device *dev)
{ "PROM", load_vbios_prom, false },
{ "ACPI", load_vbios_acpi, true },
{ "PCIROM", load_vbios_pci, true },
};
#define NUM_SHADOW_METHODS ARRAY_SIZE(shadow_methods)
static bool NVShadowVBIOS(struct drm_device *dev, uint8_t *data)
{ {
struct methods *methods = shadow_methods; struct methods shadow_methods[] = {
int testscore = 3; { "PRAMIN", bios_shadow_pramin, true, 0, 0, NULL },
int scores[NUM_SHADOW_METHODS], i; { "PROM", bios_shadow_prom, false, 0, 0, NULL },
{ "ACPI", bios_shadow_acpi, true, 0, 0, NULL },
{ "PCIROM", bios_shadow_pci, true, 0, 0, NULL },
{}
};
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nvbios *bios = &dev_priv->vbios;
struct methods *mthd, *best;
if (nouveau_vbios) { if (nouveau_vbios) {
for (i = 0; i < NUM_SHADOW_METHODS; i++) mthd = shadow_methods;
if (!strcasecmp(nouveau_vbios, methods[i].desc)) do {
break; if (strcasecmp(nouveau_vbios, mthd->desc))
continue;
if (i < NUM_SHADOW_METHODS) { NV_INFO(dev, "VBIOS source: %s\n", mthd->desc);
NV_INFO(dev, "Attempting to use BIOS image from %s\n",
methods[i].desc);
methods[i].loadbios(dev, data); mthd->shadow(bios);
if (score_vbios(dev, data, methods[i].rw)) mthd->score = score_vbios(bios, mthd->rw);
if (mthd->score)
return true; return true;
} } while ((++mthd)->shadow);
NV_ERROR(dev, "VBIOS source \'%s\' invalid\n", nouveau_vbios); NV_ERROR(dev, "VBIOS source \'%s\' invalid\n", nouveau_vbios);
} }
for (i = 0; i < NUM_SHADOW_METHODS; i++) { mthd = shadow_methods;
NV_TRACE(dev, "Attempting to load BIOS image from %s\n", do {
methods[i].desc); NV_TRACE(dev, "Checking %s for VBIOS\n", mthd->desc);
data[0] = data[1] = 0; /* avoid reuse of previous image */ mthd->shadow(bios);
methods[i].loadbios(dev, data); mthd->score = score_vbios(bios, mthd->rw);
scores[i] = score_vbios(dev, data, methods[i].rw); mthd->size = bios->length;
if (scores[i] == testscore) mthd->data = bios->data;
return true; } 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);
while (--testscore > 0) { if (best->score) {
for (i = 0; i < NUM_SHADOW_METHODS; i++) { NV_TRACE(dev, "Using VBIOS from %s\n", best->desc);
if (scores[i] == testscore) { bios->length = best->size;
NV_TRACE(dev, "Using BIOS image from %s\n", bios->data = best->data;
methods[i].desc);
methods[i].loadbios(dev, data);
return true; return true;
} }
}
}
NV_ERROR(dev, "No valid BIOS image found\n"); NV_ERROR(dev, "No valid VBIOS image found\n");
return false; return false;
} }
...@@ -6334,11 +6371,7 @@ static bool NVInitVBIOS(struct drm_device *dev) ...@@ -6334,11 +6371,7 @@ static bool NVInitVBIOS(struct drm_device *dev)
spin_lock_init(&bios->lock); spin_lock_init(&bios->lock);
bios->dev = dev; bios->dev = dev;
if (!NVShadowVBIOS(dev, bios->data)) return bios_shadow(dev);
return false;
bios->length = NV_PROM_SIZE;
return true;
} }
static int nouveau_parse_vbios_struct(struct drm_device *dev) static int nouveau_parse_vbios_struct(struct drm_device *dev)
...@@ -6498,6 +6531,10 @@ nouveau_bios_init(struct drm_device *dev) ...@@ -6498,6 +6531,10 @@ nouveau_bios_init(struct drm_device *dev)
void void
nouveau_bios_takedown(struct drm_device *dev) nouveau_bios_takedown(struct drm_device *dev)
{ {
struct drm_nouveau_private *dev_priv = dev->dev_private;
nouveau_mxm_fini(dev); nouveau_mxm_fini(dev);
nouveau_i2c_fini(dev); nouveau_i2c_fini(dev);
kfree(dev_priv->vbios.data);
} }
...@@ -211,6 +211,8 @@ struct nvbios { ...@@ -211,6 +211,8 @@ struct nvbios {
NVBIOS_BIT NVBIOS_BIT
} type; } type;
uint16_t offset; uint16_t offset;
uint32_t length;
uint8_t *data;
uint8_t chip_version; uint8_t chip_version;
...@@ -221,8 +223,6 @@ struct nvbios { ...@@ -221,8 +223,6 @@ struct nvbios {
spinlock_t lock; spinlock_t lock;
uint8_t data[NV_PROM_SIZE];
unsigned int length;
bool execute; bool execute;
uint8_t major_version; uint8_t major_version;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册