Home | History | Annotate | Line # | Download | only in bios
      1 /*	$NetBSD: nouveau_nvkm_subdev_bios_shadow.c,v 1.4 2021/12/18 23:45:38 riastradh Exp $	*/
      2 
      3 /*
      4  * Copyright 2014 Red Hat Inc.
      5  *
      6  * Permission is hereby granted, free of charge, to any person obtaining a
      7  * copy of this software and associated documentation files (the "Software"),
      8  * to deal in the Software without restriction, including without limitation
      9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
     10  * and/or sell copies of the Software, and to permit persons to whom the
     11  * Software is furnished to do so, subject to the following conditions:
     12  *
     13  * The above copyright notice and this permission notice shall be included in
     14  * all copies or substantial portions of the Software.
     15  *
     16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
     19  * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
     20  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
     21  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
     22  * OTHER DEALINGS IN THE SOFTWARE.
     23  *
     24  * Authors: Ben Skeggs <bskeggs (at) redhat.com>
     25  */
     26 #include <sys/cdefs.h>
     27 __KERNEL_RCSID(0, "$NetBSD: nouveau_nvkm_subdev_bios_shadow.c,v 1.4 2021/12/18 23:45:38 riastradh Exp $");
     28 
     29 #include "priv.h"
     30 
     31 #include <core/option.h>
     32 #include <subdev/bios.h>
     33 #include <subdev/bios/image.h>
     34 
     35 struct shadow {
     36 	u32 skip;
     37 	const struct nvbios_source *func;
     38 	void *data;
     39 	u32 size;
     40 	int score;
     41 };
     42 
     43 static bool
     44 shadow_fetch(struct nvkm_bios *bios, struct shadow *mthd, u32 upto)
     45 {
     46 	const u32 limit = (upto + 3) & ~3;
     47 	const u32 start = bios->size;
     48 	void *data = mthd->data;
     49 	if (nvbios_extend(bios, limit) > 0) {
     50 		u32 read = mthd->func->read(data, start, limit - start, bios);
     51 		bios->size = start + read;
     52 	}
     53 	return bios->size >= upto;
     54 }
     55 
     56 static int
     57 shadow_image(struct nvkm_bios *bios, int idx, u32 offset, struct shadow *mthd)
     58 {
     59 	struct nvkm_subdev *subdev = &bios->subdev;
     60 	struct nvbios_image image;
     61 	int score = 1;
     62 
     63 	if (mthd->func->no_pcir) {
     64 		image.base = 0;
     65 		image.type = 0;
     66 		image.size = mthd->func->size(mthd->data);
     67 		image.last = 1;
     68 	} else {
     69 		if (!shadow_fetch(bios, mthd, offset + 0x1000)) {
     70 			nvkm_debug(subdev, "%08x: header fetch failed\n",
     71 				   offset);
     72 			return 0;
     73 		}
     74 
     75 		if (!nvbios_image(bios, idx, &image)) {
     76 			nvkm_debug(subdev, "image %d invalid\n", idx);
     77 			return 0;
     78 		}
     79 	}
     80 	nvkm_debug(subdev, "%08x: type %02x, %d bytes\n",
     81 		   image.base, image.type, image.size);
     82 
     83 	if (!shadow_fetch(bios, mthd, image.size)) {
     84 		nvkm_debug(subdev, "%08x: fetch failed\n", image.base);
     85 		return 0;
     86 	}
     87 
     88 	switch (image.type) {
     89 	case 0x00:
     90 		if (!mthd->func->ignore_checksum &&
     91 		    nvbios_checksum(&bios->data[image.base], image.size)) {
     92 			nvkm_debug(subdev, "%08x: checksum failed\n",
     93 				   image.base);
     94 			if (!mthd->func->require_checksum) {
     95 				if (mthd->func->rw)
     96 					score += 1;
     97 				score += 1;
     98 			} else
     99 				return 0;
    100 		} else {
    101 			score += 3;
    102 		}
    103 		break;
    104 	default:
    105 		score += 3;
    106 		break;
    107 	}
    108 
    109 	if (!image.last)
    110 		score += shadow_image(bios, idx + 1, offset + image.size, mthd);
    111 	return score;
    112 }
    113 
    114 static int
    115 shadow_method(struct nvkm_bios *bios, struct shadow *mthd, const char *name)
    116 {
    117 	const struct nvbios_source *func = mthd->func;
    118 	struct nvkm_subdev *subdev = &bios->subdev;
    119 	if (func->name) {
    120 		nvkm_debug(subdev, "trying %s...\n", name ? name : func->name);
    121 		if (func->init) {
    122 			mthd->data = func->init(bios, name);
    123 			if (IS_ERR(mthd->data)) {
    124 				mthd->data = NULL;
    125 				return 0;
    126 			}
    127 		}
    128 		mthd->score = shadow_image(bios, 0, 0, mthd);
    129 		if (func->fini)
    130 			func->fini(mthd->data);
    131 		nvkm_debug(subdev, "scored %d\n", mthd->score);
    132 		mthd->data = bios->data;
    133 		mthd->size = bios->size;
    134 		bios->data  = NULL;
    135 		bios->size  = 0;
    136 	}
    137 	return mthd->score;
    138 }
    139 
    140 static u32
    141 shadow_fw_read(void *data, u32 offset, u32 length, struct nvkm_bios *bios)
    142 {
    143 	const struct firmware *fw = data;
    144 	if (offset + length <= fw->size) {
    145 		memcpy(bios->data + offset, fw->data + offset, length);
    146 		return length;
    147 	}
    148 	return 0;
    149 }
    150 
    151 static void *
    152 shadow_fw_init(struct nvkm_bios *bios, const char *name)
    153 {
    154 	struct device *dev = bios->subdev.device->dev;
    155 	const struct firmware *fw;
    156 	int ret = request_firmware(&fw, name, dev);
    157 	if (ret)
    158 		return ERR_PTR(-ENOENT);
    159 	return __UNCONST(fw);
    160 }
    161 
    162 static const struct nvbios_source
    163 shadow_fw = {
    164 	.name = "firmware",
    165 	.init = shadow_fw_init,
    166 	.fini = (void(*)(void *))release_firmware,
    167 	.read = shadow_fw_read,
    168 	.rw = false,
    169 };
    170 
    171 int
    172 nvbios_shadow(struct nvkm_bios *bios)
    173 {
    174 	struct nvkm_subdev *subdev = &bios->subdev;
    175 	struct nvkm_device *device = subdev->device;
    176 	struct shadow mthds[] = {
    177 		{ 0, &nvbios_of },
    178 		{ 0, &nvbios_ramin },
    179 		{ 0, &nvbios_rom },
    180 		{ 0, &nvbios_acpi_fast },
    181 		{ 4, &nvbios_acpi_slow },
    182 		{ 1, &nvbios_pcirom },
    183 		{ 1, &nvbios_platform },
    184 		{}
    185 	}, *mthd, *best = NULL;
    186 	const char *optarg;
    187 	char *source;
    188 	int optlen;
    189 
    190 	/* handle user-specified bios source */
    191 	optarg = nvkm_stropt(device->cfgopt, "NvBios", &optlen);
    192 	source = optarg ? kstrndup(optarg, optlen, GFP_KERNEL) : NULL;
    193 	if (source) {
    194 		/* try to match one of the built-in methods */
    195 		for (mthd = mthds; mthd->func; mthd++) {
    196 			if (mthd->func->name &&
    197 			    !strcasecmp(source, mthd->func->name)) {
    198 				best = mthd;
    199 				if (shadow_method(bios, mthd, NULL))
    200 					break;
    201 			}
    202 		}
    203 
    204 		/* otherwise, attempt to load as firmware */
    205 		if (!best && (best = mthd)) {
    206 			mthd->func = &shadow_fw;
    207 			shadow_method(bios, mthd, source);
    208 			mthd->func = NULL;
    209 		}
    210 
    211 		if (!best->score) {
    212 			nvkm_error(subdev, "%s invalid\n", source);
    213 			kfree(source);
    214 			source = NULL;
    215 		}
    216 	}
    217 
    218 	/* scan all potential bios sources, looking for best image */
    219 	if (!best || !best->score) {
    220 		for (mthd = mthds, best = mthd; mthd->func; mthd++) {
    221 			if (!mthd->skip || best->score < mthd->skip) {
    222 				if (shadow_method(bios, mthd, NULL)) {
    223 					if (mthd->score > best->score)
    224 						best = mthd;
    225 				}
    226 			}
    227 		}
    228 	}
    229 
    230 	/* cleanup the ones we didn't use */
    231 	for (mthd = mthds; mthd->func; mthd++) {
    232 		if (mthd != best)
    233 			kfree(mthd->data);
    234 	}
    235 
    236 	if (!best->score) {
    237 		nvkm_error(subdev, "unable to locate usable image\n");
    238 		return -EINVAL;
    239 	}
    240 
    241 	nvkm_debug(subdev, "using image from %s\n", best->func ?
    242 		   best->func->name : source);
    243 	bios->data = best->data;
    244 	bios->size = best->size;
    245 	kfree(source);
    246 	return 0;
    247 }
    248