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@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
shadow_fetch(struct nvkm_bios * bios,struct shadow * mthd,u32 upto)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
shadow_image(struct nvkm_bios * bios,int idx,u32 offset,struct shadow * mthd)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
shadow_method(struct nvkm_bios * bios,struct shadow * mthd,const char * name)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
shadow_fw_read(void * data,u32 offset,u32 length,struct nvkm_bios * bios)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 *
shadow_fw_init(struct nvkm_bios * bios,const char * name)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
nvbios_shadow(struct nvkm_bios * bios)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