xref: /aosp_15_r20/external/vboot_reference/futility/updater_quirks.c (revision 8617a60d3594060b7ecbd21bc622a7c14f3cf2bc)
1 /* Copyright 2018 The ChromiumOS Authors
2  * Use of this source code is governed by a BSD-style license that can be
3  * found in the LICENSE file.
4  *
5  * The board-specific quirks needed by firmware updater.
6  */
7 
8 #include <assert.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 
14 #include "cbfstool.h"
15 #include "crossystem.h"
16 #include "futility.h"
17 #include "host_misc.h"
18 #include "platform_csme.h"
19 #include "updater.h"
20 
21 struct quirks_record {
22 	const char * const match;
23 	const char * const quirks;
24 };
25 
26 /*
27  * The 'match by firmware name' is now deprecated. Please do not add any
28  * new records below. We now support reading quirks from CBFS, which is
29  * easier and more reliable. To do that, create a text file 'updater_quirks'
30  * and install to the CBFS.
31  *
32  * Examples: CL:*3365287, CL:*3351831, CL:*4441527
33  */
34 static const struct quirks_record quirks_records[] = {
35 	{ .match = "Google_Eve.",
36 	  .quirks = "unlock_csme_eve,eve_smm_store" },
37 
38 	{ .match = "Google_Poppy.", .quirks = "min_platform_version=6" },
39 	{ .match = "Google_Scarlet.", .quirks = "min_platform_version=1" },
40 	{ .match = "Google_Trogdor.", .quirks = "min_platform_version=2" },
41 
42         /* Legacy custom label units. */
43 
44 	/* reference design: octopus */
45 	{ .match = "Google_Phaser.", .quirks = "override_custom_label" },
46 };
47 
48 /*
49  * Returns True if the system has EC software sync enabled.
50  */
is_ec_software_sync_enabled(struct updater_config * cfg)51 static int is_ec_software_sync_enabled(struct updater_config *cfg)
52 {
53 	const struct vb2_gbb_header *gbb;
54 
55 	int vdat_flags = dut_get_property_int("vdat_flags", cfg);
56 	if (vdat_flags < 0) {
57 		WARN("Failed to identify DUT vdat_flags.\n");
58 		return 0;
59 	}
60 
61 	/* Check if current system has disabled software sync or no support. */
62 	if (!(vdat_flags & VBSD_EC_SOFTWARE_SYNC)) {
63 		INFO("EC Software Sync is not available.\n");
64 		return 0;
65 	}
66 
67 	/* Check if the system has been updated to disable software sync. */
68 	gbb = find_gbb(&cfg->image);
69 	if (!gbb) {
70 		WARN("Invalid AP firmware image.\n");
71 		return 0;
72 	}
73 	if (gbb->flags & VB2_GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC) {
74 		INFO("EC Software Sync will be disabled in next boot.\n");
75 		return 0;
76 	}
77 	return 1;
78 }
79 
80 /*
81  * Schedules an EC RO software sync (in next boot) if applicable.
82  */
ec_ro_software_sync(struct updater_config * cfg)83 static int ec_ro_software_sync(struct updater_config *cfg)
84 {
85 	const char *ec_ro_path;
86 	uint8_t *ec_ro_data;
87 	uint32_t ec_ro_len;
88 	int is_same_ec_ro;
89 	struct firmware_section ec_ro_sec;
90 	const char *image_file = get_firmware_image_temp_file(
91 			&cfg->image, &cfg->tempfiles);
92 
93 	if (!image_file)
94 		return 1;
95 	find_firmware_section(&ec_ro_sec, &cfg->ec_image, "EC_RO");
96 	if (!ec_ro_sec.data || !ec_ro_sec.size) {
97 		ERROR("EC image has invalid section '%s'.\n", "EC_RO");
98 		return 1;
99 	}
100 
101 	ec_ro_path = create_temp_file(&cfg->tempfiles);
102 	if (!ec_ro_path) {
103 		ERROR("Failed to create temp file.\n");
104 		return 1;
105 	}
106 	if (cbfstool_extract(image_file, FMAP_RO_CBFS, "ecro", ec_ro_path) ||
107 	    !cbfstool_file_exists(image_file, FMAP_RO_CBFS, "ecro.hash")) {
108 		INFO("No valid EC RO for software sync in AP firmware.\n");
109 		return 1;
110 	}
111 	if (vb2_read_file(ec_ro_path, &ec_ro_data, &ec_ro_len) != VB2_SUCCESS) {
112 		ERROR("Failed to read EC RO.\n");
113 		return 1;
114 	}
115 
116 	is_same_ec_ro = (ec_ro_len <= ec_ro_sec.size &&
117 			 memcmp(ec_ro_sec.data, ec_ro_data, ec_ro_len) == 0);
118 	free(ec_ro_data);
119 
120 	if (!is_same_ec_ro) {
121 		/* TODO(hungte) If change AP RO is not a problem (hash will be
122 		 * different, which may be a problem to factory and HWID), or if
123 		 * we can be be sure this is for developers, extract EC RO and
124 		 * update AP RO CBFS to trigger EC RO sync with new EC.
125 		 */
126 		ERROR("The EC RO contents specified from AP (--image) and EC "
127 		      "(--ec_image) firmware images are different, cannot "
128 		      "update by EC RO software sync.\n");
129 		return 1;
130 	}
131 	dut_set_property_int("try_ro_sync", 1, cfg);
132 	return 0;
133 }
134 
135 /*
136  * Returns True if EC is running in RW.
137  */
is_ec_in_rw(struct updater_config * cfg)138 static int is_ec_in_rw(struct updater_config *cfg)
139 {
140 	char buf[VB_MAX_STRING_PROPERTY];
141 	return (dut_get_property_string("ecfw_act", buf, sizeof(buf), cfg) == 0
142 		&& strcasecmp(buf, "RW") == 0);
143 }
144 
145 /*
146  * Quirk to enlarge a firmware image to match flash size. This is needed by
147  * devices using multiple SPI flash with different sizes, for example 8M and
148  * 16M. The image_to will be padded with 0xFF using the size of image_from.
149  * Returns 0 on success, otherwise failure.
150  */
quirk_enlarge_image(struct updater_config * cfg)151 static int quirk_enlarge_image(struct updater_config *cfg)
152 {
153 	struct firmware_image *image_from = &cfg->image_current,
154 			      *image_to = &cfg->image;
155 	const char *tmp_path;
156 	size_t to_write;
157 	FILE *fp;
158 
159 	if (image_from->size <= image_to->size)
160 		return 0;
161 
162 	tmp_path = get_firmware_image_temp_file(image_to, &cfg->tempfiles);
163 	if (!tmp_path)
164 		return -1;
165 
166 	VB2_DEBUG("Resize image from %u to %u.\n",
167 		  image_to->size, image_from->size);
168 	to_write = image_from->size - image_to->size;
169 	fp = fopen(tmp_path, "ab");
170 	if (!fp) {
171 		ERROR("Cannot open temporary file %s.\n", tmp_path);
172 		return -1;
173 	}
174 	while (to_write-- > 0)
175 		fputc('\xff', fp);
176 	fclose(fp);
177 	return reload_firmware_image(tmp_path, image_to);
178 }
179 
180 /*
181  * Platform specific quirks to unlock a firmware image with SI_ME (management
182  * engine). This may be useful when updating so the system has a chance to make
183  * sure SI_ME won't be corrupted on next boot before locking the Flash Master
184  * values in SI_DESC.
185  *
186  * Returns 0 on success, otherwise failure.
187  */
quirk_unlock_csme_eve(struct updater_config * cfg)188 static int quirk_unlock_csme_eve(struct updater_config *cfg)
189 {
190 	return unlock_csme_eve(&cfg->image);
191 }
192 
quirk_unlock_csme(struct updater_config * cfg)193 static int quirk_unlock_csme(struct updater_config *cfg)
194 {
195 	return unlock_csme(cfg);
196 }
197 
198 /*
199  * Checks and returns 0 if the platform version of current system is larger
200  * or equal to given number, otherwise non-zero.
201  */
quirk_min_platform_version(struct updater_config * cfg)202 static int quirk_min_platform_version(struct updater_config *cfg)
203 {
204 	int min_version = get_config_quirk(QUIRK_MIN_PLATFORM_VERSION, cfg);
205 	int platform_version = dut_get_property(DUT_PROP_PLATFORM_VER, cfg);
206 
207 	VB2_DEBUG("Minimum required version=%d, current platform version=%d\n",
208 		  min_version, platform_version);
209 
210 	if (platform_version >= min_version)
211 		return 0;
212 	ERROR("Need platform version >= %d (current is %d). "
213 	      "This firmware will only run on newer systems.\n",
214 	      min_version, platform_version);
215 	return -1;
216 }
217 
218 /*
219  * Quirk to help preserving SMM store on devices without a dedicated "SMMSTORE"
220  * FMAP section. These devices will store "smm_store" file in same CBFS where
221  * the legacy boot loader lives (i.e, FMAP RW_LEGACY).
222  * Note this currently has dependency on external program "cbstool".
223  * Returns 0 if the SMM store is properly preserved, or if the system is not
224  * available to do that (problem in cbfstool, or no "smm_store" in current
225  * system firmware). Otherwise non-zero as failure.
226  */
quirk_eve_smm_store(struct updater_config * cfg)227 static int quirk_eve_smm_store(struct updater_config *cfg)
228 {
229 	const char *smm_store_name = "smm_store";
230 	const char *old_store;
231 	char *command;
232 	const char *temp_image = get_firmware_image_temp_file(
233 			&cfg->image_current, &cfg->tempfiles);
234 
235 	if (!temp_image)
236 		return -1;
237 
238 	old_store = create_temp_file(&cfg->tempfiles);
239 	if (!old_store) {
240 		ERROR("Failed to create temp file.\n");
241 		return 1;
242 	}
243 	if (cbfstool_extract(temp_image, FMAP_RW_LEGACY, smm_store_name,
244 			     old_store)) {
245 		VB2_DEBUG("cbfstool failure or SMM store not available. "
246 			  "Don't preserve.\n");
247 		return 0;
248 	}
249 
250 	/* Reuse temp_image */
251 	temp_image = get_firmware_image_temp_file(&cfg->image, &cfg->tempfiles);
252 	if (!temp_image)
253 		return -1;
254 
255 	/* crosreview.com/1165109: The offset is fixed at 0x1bf000. */
256 	ASPRINTF(&command,
257 		 "cbfstool \"%s\" remove -r %s -n \"%s\" 2>/dev/null; "
258 		 "cbfstool \"%s\" add -r %s -n \"%s\" -f \"%s\" "
259 		 " -t raw -b 0x1bf000", temp_image, FMAP_RW_LEGACY,
260 		 smm_store_name, temp_image, FMAP_RW_LEGACY,
261 		 smm_store_name, old_store);
262 	free(host_shell(command));
263 	free(command);
264 
265 	return reload_firmware_image(temp_image, &cfg->image);
266 }
267 
268 /*
269  * Update EC (RO+RW) in most reliable way.
270  *
271  * Some EC will reset TCPC when doing sysjump, and will make rootfs unavailable
272  * if the system was boot from USB, or other unexpected issues even if the
273  * system was boot from internal disk. To prevent that, try to partial update
274  * only RO and expect EC software sync to update RW later, or perform EC RO
275  * software sync.
276  *
277  * Note: EC RO software sync was not fully tested and may cause problems
278  *       (b/218612817, b/187789991).
279  *       RO-update (without extra sysjump) needs support from flashrom and is
280  *       currently disabled.
281  *
282  * Returns:
283  *  EC_RECOVERY_FULL to indicate a full recovery is needed.
284  *  EC_RECOVERY_RO to indicate partial update (WP_RO) is needed.
285  *  EC_RECOVERY_DONE to indicate EC RO software sync is applied.
286  *  Other values to report failure.
287  */
quirk_ec_partial_recovery(struct updater_config * cfg)288 static int quirk_ec_partial_recovery(struct updater_config *cfg)
289 {
290 	/*
291 	 * http://crbug.com/1024401: Some EC needs extra header outside EC_RO so
292 	 * we have to update whole WP_RO, not just EC_RO.
293 	 */
294 	const char *ec_ro = "WP_RO";
295 	struct firmware_image *ec_image = &cfg->ec_image;
296 	int do_partial = get_config_quirk(QUIRK_EC_PARTIAL_RECOVERY, cfg);
297 
298 	if (!do_partial) {
299 		/* Need full update. */
300 	} else if (!firmware_section_exists(ec_image, ec_ro)) {
301 		INFO("EC image does not have section '%s'.\n", ec_ro);
302 		/* Need full update. */
303 	} else if (!is_ec_software_sync_enabled(cfg)) {
304 		/* Message already printed, need full update. */
305 	} else if (is_ec_in_rw(cfg)) {
306 		WARN("EC Software Sync detected, will only update EC RO. "
307 		     "The contents in EC RW will be updated after reboot.\n");
308 		return EC_RECOVERY_RO;
309 	} else if (ec_ro_software_sync(cfg) == 0) {
310 		INFO("EC RO and RW should be updated after reboot.\n");
311 		return EC_RECOVERY_DONE;
312 	}
313 
314 	WARN("Update EC RO+RW and may cause unexpected error later. "
315 	     "See http://crbug.com/782427#c4 for more information.\n");
316 	return EC_RECOVERY_FULL;
317 }
318 
319 /*
320  * Preserve ME during firmware update.
321  *
322  * Updating ME region while SoC is in S0 state is an unsupported use-case. On
323  * recent platforms, we are seeing issues more frequently because of this use-
324  * case. For the firmware updates performed for autoupdate firmware updates,
325  * preserve the ME region so that it gets updated in the successive boot.
326  *
327  * Returns:
328  *   1 to signal ME needs to be preserved.
329  *   0 to signal ME does not need to be preserved.
330  */
quirk_preserve_me(struct updater_config * cfg)331 static int quirk_preserve_me(struct updater_config *cfg)
332 {
333 	/*
334 	 * Only preserve the ME if performing an autoupdate-mode firmware
335 	 * update. Recovery, factory and any other update modes cannot leave the
336 	 * ME as is. Otherwise, a recovery firmware update cannot be relied upon
337 	 * to update the ME to a valid version for WP-disabled devices.
338 	 */
339 	if (cfg->try_update == TRY_UPDATE_OFF) {
340 		INFO("No auto-update requested. Not preserving ME.\n");
341 		return 0;
342 	}
343 	INFO("Auto-update requested. Preserving ME.\n");
344 
345 	/*
346 	 * b/213706510: subratabanik@ confirmed CSE may modify itself while we
347 	 * are doing system update, and currently the 'preserve' is done by
348 	 * flashing the same (e.g., "previously read") contents to skip erasing
349 	 * and writing; so we have to use the diff image to prevent contents
350 	 * being changed when writing.
351 	 */
352 	cfg->use_diff_image = 1;
353 
354 	return 1;
355 }
356 
quirk_clear_mrc_data(struct updater_config * cfg)357 static int quirk_clear_mrc_data(struct updater_config *cfg)
358 {
359 	struct firmware_section section;
360 	struct firmware_image *image = &cfg->image_current;
361 	int i, count = 0;
362 	int flash_now = 0;
363 
364 	/*
365 	 * Devices with multiple MRC caches (RECOVERY, RW, RW_VAR) will have the
366 	 * UNIFIED_MRC_CACHE; and devices with single RW cache will only have
367 	 * RW_MRC_CACHE (for example MediaTek devices).
368 	 */
369 	const char * const mrc_names[] = {
370 		"UNIFIED_MRC_CACHE",
371 		"RW_MRC_CACHE",
372 	};
373 
374 	if (is_ap_write_protection_enabled(cfg) || cfg->try_update)
375 		flash_now = 1;
376 
377 	for (i = 0; i < ARRAY_SIZE(mrc_names); i++) {
378 		const char *name = mrc_names[i];
379 
380 		find_firmware_section(&section, image, name);
381 		if (!section.size)
382 			continue;
383 
384 		WARN("Wiping memory training data: %s\n", name);
385 		memset(section.data, 0xff, section.size);
386 		if (flash_now) {
387 			const char *write_names[] = {name};
388 			write_system_firmware(cfg, image, write_names,
389 					      ARRAY_SIZE(write_names));
390 		}
391 		count++;
392 		break;
393 	}
394 
395 	if (count)
396 		WARN("Next boot will take a few mins for memory training.\n");
397 	else
398 		ERROR("No known memory training data in the firmware image.\n");
399 
400 	return 0;
401 }
402 
403 /*
404  * Disable checking platform compatibility.
405  */
quirk_no_check_platform(struct updater_config * cfg)406 static int quirk_no_check_platform(struct updater_config *cfg)
407 {
408 	WARN("Disabled checking platform. You are on your own.\n");
409 	cfg->check_platform = 0;
410 	return 0;
411 }
412 
413 /*
414  * Disable verifying contents after flashing.
415  */
quirk_no_verify(struct updater_config * cfg)416 static int quirk_no_verify(struct updater_config *cfg)
417 {
418 	WARN("Disabled verifying flashed contents. You are on your own.\n");
419 	cfg->do_verify = 0;
420 	return 0;
421 }
422 
updater_register_quirks(struct updater_config * cfg)423 void updater_register_quirks(struct updater_config *cfg)
424 {
425 	struct quirk_entry *quirks;
426 
427 	assert(ARRAY_SIZE(cfg->quirks) == QUIRK_MAX);
428 	quirks = &cfg->quirks[QUIRK_ENLARGE_IMAGE];
429 	quirks->name = "enlarge_image";
430 	quirks->help = "Enlarge firmware image by flash size.";
431 	quirks->apply = quirk_enlarge_image;
432 
433 	quirks = &cfg->quirks[QUIRK_MIN_PLATFORM_VERSION];
434 	quirks->name = "min_platform_version";
435 	quirks->help = "Minimum compatible platform version "
436 			"(also known as Board ID version).";
437 	quirks->apply = quirk_min_platform_version;
438 
439 	quirks = &cfg->quirks[QUIRK_UNLOCK_CSME_EVE];
440 	quirks->name = "unlock_csme_eve";
441 	quirks->help = "b/35568719; (skl, kbl) only lock management engine in board-postinst.";
442 	quirks->apply = quirk_unlock_csme_eve;
443 
444 	quirks = &cfg->quirks[QUIRK_UNLOCK_CSME];
445 	quirks->name = "unlock_csme";
446 	quirks->help = "b/273168873; unlock the Intel management engine. "
447 			"Applies to all recent Intel platforms (CML onwards)";
448 	quirks->apply = quirk_unlock_csme;
449 
450 	quirks = &cfg->quirks[QUIRK_EVE_SMM_STORE];
451 	quirks->name = "eve_smm_store";
452 	quirks->help = "b/70682365; preserve UEFI SMM store without "
453 		       "dedicated FMAP section.";
454 	quirks->apply = quirk_eve_smm_store;
455 
456 	quirks = &cfg->quirks[QUIRK_EC_PARTIAL_RECOVERY];
457 	quirks->name = "ec_partial_recovery";
458 	quirks->help = "chromium/1024401; recover EC by partial RO update.";
459 	quirks->apply = quirk_ec_partial_recovery;
460 
461 	quirks = &cfg->quirks[QUIRK_OVERRIDE_CUSTOM_LABEL];
462 	quirks->name = "override_custom_label";
463 	quirks->help = "b/146876241; override custom label name for "
464 			"devices shipped with different root key.";
465 	quirks->apply = NULL; /* Simple config. */
466 
467 	quirks = &cfg->quirks[QUIRK_PRESERVE_ME];
468 	quirks->name = "preserve_me";
469 	quirks->help = "b/165590952; Preserve ME during firmware update except "
470 		       "for factory update or developer images.";
471 	quirks->apply = quirk_preserve_me;
472 
473 	quirks = &cfg->quirks[QUIRK_NO_CHECK_PLATFORM];
474 	quirks->name = "no_check_platform";
475 	quirks->help = "Do not check platform name.";
476 	quirks->apply = quirk_no_check_platform;
477 
478 	quirks = &cfg->quirks[QUIRK_NO_VERIFY];
479 	quirks->name = "no_verify";
480 	quirks->help = "Do not verify when flashing.";
481 	quirks->apply = quirk_no_verify;
482 
483 	quirks = &cfg->quirks[QUIRK_EXTRA_RETRIES];
484 	quirks->name = "extra_retries";
485 	quirks->help = "Extra retries when writing to system firmware.";
486 	quirks->apply = NULL;  /* Simple config. */
487 
488 	quirks = &cfg->quirks[QUIRK_CLEAR_MRC_DATA];
489 	quirks->name = "clear_mrc_data";
490 	quirks->help = "b/255617349: Clear memory training data (MRC).";
491 	quirks->apply = quirk_clear_mrc_data;
492 }
493 
updater_get_model_quirks(struct updater_config * cfg)494 const char * const updater_get_model_quirks(struct updater_config *cfg)
495 {
496 	const char *pattern = cfg->image.ro_version;
497 	int i;
498 
499 	if (!pattern) {
500 		VB2_DEBUG("Cannot identify system for default quirks.\n");
501 		return NULL;
502 	}
503 
504 	for (i = 0; i < ARRAY_SIZE(quirks_records); i++) {
505 		const struct quirks_record *r = &quirks_records[i];
506 		if (strncmp(r->match, pattern, strlen(r->match)) != 0)
507 		    continue;
508 		VB2_DEBUG("Found system default quirks: %s\n", r->quirks);
509 		return r->quirks;
510 	}
511 	return NULL;
512 }
513 
updater_get_cbfs_quirks(struct updater_config * cfg)514 char *updater_get_cbfs_quirks(struct updater_config *cfg)
515 {
516 	const char *entry_name = "updater_quirks";
517 	const char *cbfs_region = "FW_MAIN_A";
518 	struct firmware_section cbfs_section;
519 
520 	/* Before invoking cbfstool, try to search for CBFS file name. */
521 	find_firmware_section(&cbfs_section, &cfg->image, cbfs_region);
522 	if (!cbfs_section.size || !memmem(cbfs_section.data, cbfs_section.size,
523 					  entry_name, strlen(entry_name))) {
524 		if (!cbfs_section.size)
525 			VB2_DEBUG("Missing region: %s\n", cbfs_region);
526 		else
527 			VB2_DEBUG("Cannot find entry: %s\n", entry_name);
528 		return NULL;
529 	}
530 
531 	const char *image_file = get_firmware_image_temp_file(
532 			&cfg->image, &cfg->tempfiles);
533 	uint8_t *data = NULL;
534 	uint32_t size = 0;
535 	const char *entry_file;
536 
537 	/* Although the name exists, it may not be a real file. */
538 	if (!cbfstool_file_exists(image_file, cbfs_region, entry_name)) {
539 		VB2_DEBUG("Found string '%s' but not a file.\n", entry_name);
540 		return NULL;
541 	}
542 
543 	VB2_DEBUG("Found %s from CBFS %s\n", entry_name, cbfs_region);
544 	entry_file = create_temp_file(&cfg->tempfiles);
545 	if (!entry_file) {
546 		ERROR("Failed to create temp file.\n");
547 		return NULL;
548 	}
549 	if (cbfstool_extract(image_file, cbfs_region, entry_name, entry_file) ||
550 	    vb2_read_file(entry_file, &data, &size) != VB2_SUCCESS) {
551 		ERROR("Failed to read [%s] from CBFS [%s].\n",
552 		      entry_name, cbfs_region);
553 		return NULL;
554 	}
555 	VB2_DEBUG("Got quirks (%u bytes): %s\n", size, data);
556 	return (char *)data;
557 }
558 
quirk_override_custom_label(struct updater_config * cfg,const struct manifest * manifest,const struct model_config * model)559 const struct model_config *quirk_override_custom_label(
560 		struct updater_config *cfg,
561 		const struct manifest *manifest,
562 		const struct model_config *model)
563 {
564 	/* If not write protected, no need to apply the hack. */
565 	if (!is_ap_write_protection_enabled(cfg)) {
566 		VB2_DEBUG("Skipped because AP not write protected.\n");
567 		return NULL;
568 	}
569 
570 	const struct firmware_image *image = &cfg->image_current;
571 	assert(image && image->data);
572 
573 	if (strcmp(model->name, "phaser360") == 0) {
574 		/* b/146876241 */
575 		const char *key_hash = get_firmware_rootkey_hash(image);
576 		const char * const DOPEFISH_KEY_HASH =
577 				"9a1f2cc319e2f2e61237dc51125e35ddd4d20984";
578 
579 		if (key_hash && strcmp(key_hash, DOPEFISH_KEY_HASH) == 0) {
580 			const char * const dopefish = "phaser360-dopefish";
581 			WARN("A Phaser360 with Dopefish rootkey - "
582 			     "override custom label to '%s'.\n", dopefish);
583 			model = manifest_find_model(cfg, manifest, dopefish);
584 			if (model)
585 				INFO("Model changed to '%s'.\n", model->name);
586 			else
587 				ERROR("No model defined for '%s'.\n", dopefish);
588 
589 			return model;
590 		}
591 	}
592 	return NULL;
593 }
594