/* * 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 */ #include "priv.h" #include #include 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; }