| /* | |
| * 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.h> | |
| #include <subdev/bios/image.h> | |
| struct shadow { | |
| u32 skip; | |
| const struct nvbios_source *func; | |
| void *data; | |
| u32 size; | |
| int score; | |
| }; | |
| static bool | |
| shadow_fetch(struct nvkm_bios *bios, struct shadow *mthd, u32 upto) | |
| { | |
| 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 >= upto; | |
| } | |
| static int | |
| shadow_image(struct nvkm_bios *bios, int idx, u32 offset, struct shadow *mthd) | |
| { | |
| struct nvkm_subdev *subdev = &bios->subdev; | |
| struct nvbios_image image; | |
| int score = 1; | |
| if (mthd->func->no_pcir) { | |
| image.base = 0; | |
| image.type = 0; | |
| image.size = mthd->func->size(mthd->data); | |
| image.last = 1; | |
| } else { | |
| if (!shadow_fetch(bios, mthd, offset + 0x1000)) { | |
| nvkm_debug(subdev, "%08x: header fetch failed\n", | |
| offset); | |
| return 0; | |
| } | |
| if (!nvbios_image(bios, idx, &image)) { | |
| nvkm_debug(subdev, "image %d invalid\n", idx); | |
| return 0; | |
| } | |
| } | |
| nvkm_debug(subdev, "%08x: type %02x, %d bytes\n", | |
| image.base, image.type, image.size); | |
| if (!shadow_fetch(bios, mthd, image.size)) { | |
| nvkm_debug(subdev, "%08x: fetch failed\n", image.base); | |
| return 0; | |
| } | |
| switch (image.type) { | |
| case 0x00: | |
| if (!mthd->func->ignore_checksum && | |
| nvbios_checksum(&bios->data[image.base], image.size)) { | |
| nvkm_debug(subdev, "%08x: checksum failed\n", | |
| image.base); | |
| if (!mthd->func->require_checksum) { | |
| if (mthd->func->rw) | |
| score += 1; | |
| score += 1; | |
| } else | |
| return 0; | |
| } else { | |
| score += 3; | |
| } | |
| break; | |
| default: | |
| score += 3; | |
| break; | |
| } | |
| if (!image.last) | |
| score += shadow_image(bios, idx + 1, offset + image.size, mthd); | |
| return score; | |
| } | |
| static int | |
| shadow_method(struct nvkm_bios *bios, struct shadow *mthd, const char *name) | |
| { | |
| const struct nvbios_source *func = mthd->func; | |
| struct nvkm_subdev *subdev = &bios->subdev; | |
| if (func->name) { | |
| nvkm_debug(subdev, "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_image(bios, 0, 0, mthd); | |
| if (func->fini) | |
| func->fini(mthd->data); | |
| nvkm_debug(subdev, "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 nvkm_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 nvkm_bios *bios, const char *name) | |
| { | |
| struct device *dev = bios->subdev.device->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 nvkm_bios *bios) | |
| { | |
| struct nvkm_subdev *subdev = &bios->subdev; | |
| struct nvkm_device *device = subdev->device; | |
| struct shadow mthds[] = { | |
| { 0, &nvbios_of }, | |
| { 0, &nvbios_ramin }, | |
| { 0, &nvbios_rom }, | |
| { 0, &nvbios_acpi_fast }, | |
| { 4, &nvbios_acpi_slow }, | |
| { 1, &nvbios_pcirom }, | |
| { 1, &nvbios_platform }, | |
| {} | |
| }, *mthd, *best = NULL; | |
| const char *optarg; | |
| char *source; | |
| int optlen; | |
| /* handle user-specified bios source */ | |
| optarg = nvkm_stropt(device->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) { | |
| nvkm_error(subdev, "%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) { | |
| nvkm_error(subdev, "unable to locate usable image\n"); | |
| return -EINVAL; | |
| } | |
| nvkm_debug(subdev, "using image from %s\n", best->func ? | |
| best->func->name : source); | |
| bios->data = best->data; | |
| bios->size = best->size; | |
| kfree(source); | |
| return 0; | |
| } |