diff options
| author | fishsoupisgood <github@madingley.org> | 2019-04-29 01:17:54 +0100 | 
|---|---|---|
| committer | fishsoupisgood <github@madingley.org> | 2019-05-27 03:43:43 +0100 | 
| commit | 3f2546b2ef55b661fd8dd69682b38992225e86f6 (patch) | |
| tree | 65ca85f13617aee1dce474596800950f266a456c /roms/ipxe/src/image | |
| download | qemu-master.tar.gz qemu-master.tar.bz2 qemu-master.zip | |
Diffstat (limited to 'roms/ipxe/src/image')
| -rw-r--r-- | roms/ipxe/src/image/efi_image.c | 316 | ||||
| -rw-r--r-- | roms/ipxe/src/image/elf.c | 185 | ||||
| -rw-r--r-- | roms/ipxe/src/image/embedded.c | 91 | ||||
| -rw-r--r-- | roms/ipxe/src/image/png.c | 1007 | ||||
| -rw-r--r-- | roms/ipxe/src/image/pnm.c | 415 | ||||
| -rw-r--r-- | roms/ipxe/src/image/script.c | 423 | ||||
| -rw-r--r-- | roms/ipxe/src/image/segment.c | 91 | 
7 files changed, 2528 insertions, 0 deletions
| diff --git a/roms/ipxe/src/image/efi_image.c b/roms/ipxe/src/image/efi_image.c new file mode 100644 index 00000000..b7d8f9c6 --- /dev/null +++ b/roms/ipxe/src/image/efi_image.c @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <errno.h> +#include <stdlib.h> +#include <wchar.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_snp.h> +#include <ipxe/efi/efi_download.h> +#include <ipxe/efi/efi_file.h> +#include <ipxe/efi/efi_utils.h> +#include <ipxe/efi/efi_strings.h> +#include <ipxe/efi/efi_wrap.h> +#include <ipxe/image.h> +#include <ipxe/init.h> +#include <ipxe/features.h> +#include <ipxe/uri.h> + +FEATURE ( FEATURE_IMAGE, "EFI", DHCP_EB_FEATURE_EFI, 1 ); + +/* Disambiguate the various error causes */ +#define EINFO_EEFI_LOAD							\ +	__einfo_uniqify ( EINFO_EPLATFORM, 0x01,			\ +			  "Could not load image" ) +#define EINFO_EEFI_LOAD_PROHIBITED					\ +	__einfo_platformify ( EINFO_EEFI_LOAD, EFI_SECURITY_VIOLATION,	\ +			      "Image prohibited by security policy" ) +#define EEFI_LOAD_PROHIBITED						\ +	__einfo_error ( EINFO_EEFI_LOAD_PROHIBITED ) +#define EEFI_LOAD( efirc ) EPLATFORM ( EINFO_EEFI_LOAD, efirc,		\ +				       EEFI_LOAD_PROHIBITED ) +#define EINFO_EEFI_START						\ +	__einfo_uniqify ( EINFO_EPLATFORM, 0x02,			\ +			  "Could not start image" ) +#define EEFI_START( efirc ) EPLATFORM ( EINFO_EEFI_START, efirc ) + +/** + * Create device path for image + * + * @v image		EFI image + * @v parent		Parent device path + * @ret path		Device path, or NULL on failure + * + * The caller must eventually free() the device path. + */ +static EFI_DEVICE_PATH_PROTOCOL * +efi_image_path ( struct image *image, EFI_DEVICE_PATH_PROTOCOL *parent ) { +	EFI_DEVICE_PATH_PROTOCOL *path; +	FILEPATH_DEVICE_PATH *filepath; +	EFI_DEVICE_PATH_PROTOCOL *end; +	size_t name_len; +	size_t prefix_len; +	size_t filepath_len; +	size_t len; + +	/* Calculate device path lengths */ +	end = efi_devpath_end ( parent ); +	prefix_len = ( ( void * ) end - ( void * ) parent ); +	name_len = strlen ( image->name ); +	filepath_len = ( SIZE_OF_FILEPATH_DEVICE_PATH + +			 ( name_len + 1 /* NUL */ ) * sizeof ( wchar_t ) ); +	len = ( prefix_len + filepath_len + sizeof ( *end ) ); + +	/* Allocate device path */ +	path = zalloc ( len ); +	if ( ! path ) +		return NULL; + +	/* Construct device path */ +	memcpy ( path, parent, prefix_len ); +	filepath = ( ( ( void * ) path ) + prefix_len ); +	filepath->Header.Type = MEDIA_DEVICE_PATH; +	filepath->Header.SubType = MEDIA_FILEPATH_DP; +	filepath->Header.Length[0] = ( filepath_len & 0xff ); +	filepath->Header.Length[1] = ( filepath_len >> 8 ); +	efi_snprintf ( filepath->PathName, ( name_len + 1 /* NUL */ ), +		       "%s", image->name ); +	end = ( ( ( void * ) filepath ) + filepath_len ); +	end->Type = END_DEVICE_PATH_TYPE; +	end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; +	end->Length[0] = sizeof ( *end ); + +	return path; +} + +/** + * Create command line for image + * + * @v image             EFI image + * @ret cmdline		Command line, or NULL on failure + */ +static wchar_t * efi_image_cmdline ( struct image *image ) { +	wchar_t *cmdline; +	size_t len; + +	len = ( strlen ( image->name ) + +		( image->cmdline ? +		  ( 1 /* " " */ + strlen ( image->cmdline ) ) : 0 ) ); +	cmdline = zalloc ( ( len + 1 /* NUL */ ) * sizeof ( wchar_t ) ); +	if ( ! cmdline ) +		return NULL; +	efi_snprintf ( cmdline, ( len + 1 /* NUL */ ), "%s%s%s", +		       image->name, +		       ( image->cmdline ? " " : "" ), +		       ( image->cmdline ? image->cmdline : "" ) ); +	return cmdline; +} + +/** + * Execute EFI image + * + * @v image		EFI image + * @ret rc		Return status code + */ +static int efi_image_exec ( struct image *image ) { +	EFI_BOOT_SERVICES *bs = efi_systab->BootServices; +	struct efi_snp_device *snpdev; +	EFI_DEVICE_PATH_PROTOCOL *path; +	union { +		EFI_LOADED_IMAGE_PROTOCOL *image; +		void *interface; +	} loaded; +	EFI_HANDLE handle; +	wchar_t *cmdline; +	EFI_STATUS efirc; +	int rc; + +	/* Find an appropriate device handle to use */ +	snpdev = last_opened_snpdev(); +	if ( ! snpdev ) { +		DBGC ( image, "EFIIMAGE %p could not identify SNP device\n", +		       image ); +		rc = -ENODEV; +		goto err_no_snpdev; +	} + +	/* Install file I/O protocols */ +	if ( ( rc = efi_file_install ( snpdev->handle ) ) != 0 ) { +		DBGC ( image, "EFIIMAGE %p could not install file protocol: " +		       "%s\n", image, strerror ( rc ) ); +		goto err_file_install; +	} + +	/* Install iPXE download protocol */ +	if ( ( rc = efi_download_install ( snpdev->handle ) ) != 0 ) { +		DBGC ( image, "EFIIMAGE %p could not install iPXE download " +		       "protocol: %s\n", image, strerror ( rc ) ); +		goto err_download_install; +	} + +	/* Create device path for image */ +	path = efi_image_path ( image, snpdev->path ); +	if ( ! path ) { +		DBGC ( image, "EFIIMAGE %p could not create device path\n", +		       image ); +		rc = -ENOMEM; +		goto err_image_path; +	} + +	/* Create command line for image */ +	cmdline = efi_image_cmdline ( image ); +	if ( ! cmdline ) { +		DBGC ( image, "EFIIMAGE %p could not create command line\n", +		       image ); +		rc = -ENOMEM; +		goto err_cmdline; +	} + +	/* Attempt loading image */ +	if ( ( efirc = bs->LoadImage ( FALSE, efi_image_handle, path, +				       user_to_virt ( image->data, 0 ), +				       image->len, &handle ) ) != 0 ) { +		/* Not an EFI image */ +		rc = -EEFI_LOAD ( efirc ); +		DBGC ( image, "EFIIMAGE %p could not load: %s\n", +		       image, strerror ( rc ) ); +		goto err_load_image; +	} + +	/* Get the loaded image protocol for the newly loaded image */ +	efirc = bs->OpenProtocol ( handle, &efi_loaded_image_protocol_guid, +				   &loaded.interface, efi_image_handle, +				   NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); +	if ( efirc ) { +		/* Should never happen */ +		rc = -EEFI ( efirc ); +		goto err_open_protocol; +	} + +	/* Some EFI 1.10 implementations seem not to fill in DeviceHandle */ +	if ( loaded.image->DeviceHandle == NULL ) { +		DBGC ( image, "EFIIMAGE %p filling in missing DeviceHandle\n", +		       image ); +		loaded.image->DeviceHandle = snpdev->handle; +	} + +	/* Sanity checks */ +	assert ( loaded.image->ParentHandle == efi_image_handle ); +	assert ( loaded.image->DeviceHandle == snpdev->handle ); +	assert ( loaded.image->LoadOptionsSize == 0 ); +	assert ( loaded.image->LoadOptions == NULL ); + +	/* Set command line */ +	loaded.image->LoadOptions = cmdline; +	loaded.image->LoadOptionsSize = +		( ( wcslen ( cmdline ) + 1 /* NUL */ ) * sizeof ( wchar_t ) ); + +	/* Release network devices for use via SNP */ +	efi_snp_release(); + +	/* Wrap calls made by the loaded image (for debugging) */ +	efi_wrap ( handle ); + +	/* Start the image */ +	if ( ( efirc = bs->StartImage ( handle, NULL, NULL ) ) != 0 ) { +		rc = -EEFI_START ( efirc ); +		DBGC ( image, "EFIIMAGE %p could not start (or returned with " +		       "error): %s\n", image, strerror ( rc ) ); +		goto err_start_image; +	} + +	/* Success */ +	rc = 0; + + err_start_image: +	efi_snp_claim(); + err_open_protocol: +	/* If there was no error, then the image must have been +	 * started and returned successfully.  It either unloaded +	 * itself, or it intended to remain loaded (e.g. it was a +	 * driver).  We therefore do not unload successful images. +	 * +	 * If there was an error, attempt to unload the image.  This +	 * may not work.  In particular, there is no way to tell +	 * whether an error returned from StartImage() was due to +	 * being unable to start the image (in which case we probably +	 * should call UnloadImage()), or due to the image itself +	 * returning an error (in which case we probably should not +	 * call UnloadImage()).  We therefore ignore any failures from +	 * the UnloadImage() call itself. +	 */ +	if ( rc != 0 ) +		bs->UnloadImage ( handle ); + err_load_image: +	free ( cmdline ); + err_cmdline: +	free ( path ); + err_image_path: +	efi_download_uninstall ( snpdev->handle ); + err_download_install: +	efi_file_uninstall ( snpdev->handle ); + err_file_install: + err_no_snpdev: +	return rc; +} + +/** + * Probe EFI image + * + * @v image		EFI file + * @ret rc		Return status code + */ +static int efi_image_probe ( struct image *image ) { +	EFI_BOOT_SERVICES *bs = efi_systab->BootServices; +	static EFI_DEVICE_PATH_PROTOCOL empty_path = { +		.Type = END_DEVICE_PATH_TYPE, +		.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE, +		.Length[0] = sizeof ( empty_path ), +	}; +	EFI_HANDLE handle; +	EFI_STATUS efirc; +	int rc; + +	/* Attempt loading image */ +	if ( ( efirc = bs->LoadImage ( FALSE, efi_image_handle, &empty_path, +				       user_to_virt ( image->data, 0 ), +				       image->len, &handle ) ) != 0 ) { +		/* Not an EFI image */ +		rc = -EEFI_LOAD ( efirc ); +		DBGC ( image, "EFIIMAGE %p could not load: %s\n", +		       image, strerror ( rc ) ); +		return rc; +	} + +	/* Unload the image.  We can't leave it loaded, because we +	 * have no "unload" operation. +	 */ +	bs->UnloadImage ( handle ); + +	return 0; +} + +/** EFI image type */ +struct image_type efi_image_type __image_type ( PROBE_NORMAL ) = { +	.name = "EFI", +	.probe = efi_image_probe, +	.exec = efi_image_exec, +}; diff --git a/roms/ipxe/src/image/elf.c b/roms/ipxe/src/image/elf.c new file mode 100644 index 00000000..51636a8e --- /dev/null +++ b/roms/ipxe/src/image/elf.c @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** + * @file + * + * ELF image format + * + * A "pure" ELF image is not a bootable image.  There are various + * bootable formats based upon ELF (e.g. Multiboot), which share + * common ELF-related functionality. + */ + +#include <errno.h> +#include <elf.h> +#include <ipxe/uaccess.h> +#include <ipxe/segment.h> +#include <ipxe/image.h> +#include <ipxe/elf.h> + +typedef Elf32_Ehdr	Elf_Ehdr; +typedef Elf32_Phdr	Elf_Phdr; +typedef Elf32_Off	Elf_Off; +#define ELFCLASS	ELFCLASS32 + +/** + * Load ELF segment into memory + * + * @v image		ELF file + * @v phdr		ELF program header + * @v ehdr		ELF executable header + * @ret entry		Entry point, if found + * @ret max		Maximum used address + * @ret rc		Return status code + */ +static int elf_load_segment ( struct image *image, Elf_Phdr *phdr, +			      Elf_Ehdr *ehdr, physaddr_t *entry, +			      physaddr_t *max ) { +	physaddr_t dest; +	physaddr_t end; +	userptr_t buffer; +	unsigned long e_offset; +	int rc; + +	/* Do nothing for non-PT_LOAD segments */ +	if ( phdr->p_type != PT_LOAD ) +		return 0; + +	/* Check segment lies within image */ +	if ( ( phdr->p_offset + phdr->p_filesz ) > image->len ) { +		DBGC ( image, "ELF %p segment outside image\n", image ); +		return -ENOEXEC; +	} + +	/* Find start address: use physical address for preference, +	 * fall back to virtual address if no physical address +	 * supplied. +	 */ +	dest = phdr->p_paddr; +	if ( ! dest ) +		dest = phdr->p_vaddr; +	if ( ! dest ) { +		DBGC ( image, "ELF %p segment loads to physical address 0\n", +		       image ); +		return -ENOEXEC; +	} +	buffer = phys_to_user ( dest ); +	end = ( dest + phdr->p_memsz ); + +	DBGC ( image, "ELF %p loading segment [%x,%x) to [%x,%x,%x)\n", image, +	       phdr->p_offset, ( phdr->p_offset + phdr->p_filesz ), +	       phdr->p_paddr, ( phdr->p_paddr + phdr->p_filesz ), +	       ( phdr->p_paddr + phdr->p_memsz ) ); + +	/* Verify and prepare segment */ +	if ( ( rc = prep_segment ( buffer, phdr->p_filesz, +				   phdr->p_memsz ) ) != 0 ) { +		DBGC ( image, "ELF %p could not prepare segment: %s\n", +		       image, strerror ( rc ) ); +		return rc; +	} + +	/* Update maximum used address, if applicable */ +	if ( end > *max ) +		*max = end; + +	/* Copy image to segment */ +	memcpy_user ( buffer, 0, image->data, phdr->p_offset, phdr->p_filesz ); + +	/* Set execution address, if it lies within this segment */ +	if ( ( e_offset = ( ehdr->e_entry - dest ) ) < phdr->p_filesz ) { +		*entry = ehdr->e_entry; +		DBGC ( image, "ELF %p found physical entry point at %lx\n", +		       image, *entry ); +	} else if ( ( e_offset = ( ehdr->e_entry - phdr->p_vaddr ) ) +		    < phdr->p_filesz ) { +		if ( ! *entry ) { +			*entry = ( dest + e_offset ); +			DBGC ( image, "ELF %p found virtual entry point at %lx" +			       " (virt %lx)\n", image, *entry, +			       ( ( unsigned long ) ehdr->e_entry ) ); +		} +	} + +	return 0; +} + +/** + * Load ELF image into memory + * + * @v image		ELF file + * @ret entry		Entry point + * @ret max		Maximum used address + * @ret rc		Return status code + */ +int elf_load ( struct image *image, physaddr_t *entry, physaddr_t *max ) { +	static const uint8_t e_ident[] = { +		[EI_MAG0]	= ELFMAG0, +		[EI_MAG1]	= ELFMAG1, +		[EI_MAG2]	= ELFMAG2, +		[EI_MAG3]	= ELFMAG3, +		[EI_CLASS]	= ELFCLASS, +	}; +	Elf_Ehdr ehdr; +	Elf_Phdr phdr; +	Elf_Off phoff; +	unsigned int phnum; +	int rc; + +	/* Read ELF header */ +	copy_from_user ( &ehdr, image->data, 0, sizeof ( ehdr ) ); +	if ( memcmp ( &ehdr.e_ident[EI_MAG0], e_ident, +		      sizeof ( e_ident ) ) != 0 ) { +		DBGC ( image, "ELF %p has invalid signature\n", image ); +		return -ENOEXEC; +	} + +	/* Initialise maximum used address */ +	*max = 0; + +	/* Invalidate entry point */ +	*entry = 0; + +	/* Read ELF program headers */ +	for ( phoff = ehdr.e_phoff , phnum = ehdr.e_phnum ; phnum ; +	      phoff += ehdr.e_phentsize, phnum-- ) { +		if ( phoff > image->len ) { +			DBGC ( image, "ELF %p program header %d outside " +			       "image\n", image, phnum ); +			return -ENOEXEC; +		} +		copy_from_user ( &phdr, image->data, phoff, sizeof ( phdr ) ); +		if ( ( rc = elf_load_segment ( image, &phdr, &ehdr, +					       entry, max ) ) != 0 ) { +			return rc; +		} +	} + +	/* Check for a valid execution address */ +	if ( ! *entry ) { +		DBGC ( image, "ELF %p entry point %lx outside image\n", +		       image, ( ( unsigned long ) ehdr.e_entry ) ); +		return -ENOEXEC; +	} + +	return 0; +} diff --git a/roms/ipxe/src/image/embedded.c b/roms/ipxe/src/image/embedded.c new file mode 100644 index 00000000..6358378f --- /dev/null +++ b/roms/ipxe/src/image/embedded.c @@ -0,0 +1,91 @@ +/** @file + * + * Embedded image support + * + * Embedded images are images built into the iPXE binary and do not require + * fetching over the network. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <string.h> +#include <ipxe/image.h> +#include <ipxe/uaccess.h> +#include <ipxe/init.h> + +/* Raw image data for all embedded images */ +#undef EMBED +#define EMBED( _index, _path, _name )					\ +	extern char embedded_image_ ## _index ## _data[];		\ +	extern char embedded_image_ ## _index ## _len[];		\ +	__asm__ ( ".section \".rodata\", \"a\", @progbits\n\t"		\ +		  "\nembedded_image_" #_index "_data:\n\t"		\ +		  ".incbin \"" _path "\"\n\t"				\ +		  "\nembedded_image_" #_index "_end:\n\t"		\ +		  ".equ embedded_image_" #_index "_len, "		\ +			"( embedded_image_" #_index "_end - "		\ +			"  embedded_image_" #_index "_data )\n\t"	\ +		  ".previous\n\t" ); +EMBED_ALL + +/* Image structures for all embedded images */ +#undef EMBED +#define EMBED( _index, _path, _name ) {					\ +	.refcnt = REF_INIT ( ref_no_free ),				\ +	.name = _name,							\ +	.data = ( userptr_t ) ( embedded_image_ ## _index ## _data ),	\ +	.len = ( size_t ) embedded_image_ ## _index ## _len,		\ +}, +static struct image embedded_images[] = { +	EMBED_ALL +}; + +/** + * Register all embedded images + */ +static void embedded_init ( void ) { +	int i; +	struct image *image; +	void *data; +	int rc; + +	/* Skip if we have no embedded images */ +	if ( ! sizeof ( embedded_images ) ) +		return; + +	/* Fix up data pointers and register images */ +	for ( i = 0 ; i < ( int ) ( sizeof ( embedded_images ) / +				    sizeof ( embedded_images[0] ) ) ; i++ ) { +		image = &embedded_images[i]; + +		/* virt_to_user() cannot be used in a static +		 * initialiser, so we cast the pointer to a userptr_t +		 * in the initialiser and fix it up here.  (This will +		 * actually be a no-op on most platforms.) +		 */ +		data = ( ( void * ) image->data ); +		image->data = virt_to_user ( data ); + +		DBG ( "Embedded image \"%s\": %zd bytes at %p\n", +		      image->name, image->len, data ); + +		if ( ( rc = register_image ( image ) ) != 0 ) { +			DBG ( "Could not register embedded image \"%s\": " +			      "%s\n", image->name, strerror ( rc ) ); +			return; +		} +	} + +	/* Select the first image */ +	image = &embedded_images[0]; +	if ( ( rc = image_select ( image ) ) != 0 ) { +		DBG ( "Could not select embedded image \"%s\": %s\n", +		      image->name, strerror ( rc ) ); +		return; +	} +} + +/** Embedded image initialisation function */ +struct init_fn embedded_init_fn __init_fn ( INIT_LATE ) = { +	.initialise = embedded_init, +}; diff --git a/roms/ipxe/src/image/png.c b/roms/ipxe/src/image/png.c new file mode 100644 index 00000000..c1460855 --- /dev/null +++ b/roms/ipxe/src/image/png.c @@ -0,0 +1,1007 @@ +/* + * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/umalloc.h> +#include <ipxe/pixbuf.h> +#include <ipxe/deflate.h> +#include <ipxe/png.h> + +/** @file + * + * Portable Network Graphics (PNG) format + * + * The PNG format is defined in RFC 2083. + */ + +/** PNG context */ +struct png_context { +	/** Offset within image */ +	size_t offset; + +	/** Pixel buffer */ +	struct pixel_buffer *pixbuf; + +	/** Bit depth */ +	unsigned int depth; +	/** Colour type */ +	unsigned int colour_type; +	/** Number of channels */ +	unsigned int channels; +	/** Number of interlace passes */ +	unsigned int passes; +	/** Palette, in iPXE's pixel buffer format */ +	uint32_t palette[PNG_PALETTE_COUNT]; + +	/** Decompression buffer for raw PNG data */ +	struct deflate_chunk raw; +	/** Decompressor */ +	struct deflate deflate; +}; + +/** A PNG interlace pass */ +struct png_interlace { +	/** Pass number */ +	unsigned int pass; +	/** X starting indent */ +	unsigned int x_indent; +	/** Y starting indent */ +	unsigned int y_indent; +	/** X stride */ +	unsigned int x_stride; +	/** Y stride */ +	unsigned int y_stride; +	/** Width */ +	unsigned int width; +	/** Height */ +	unsigned int height; +}; + +/** PNG file signature */ +static struct png_signature png_signature = PNG_SIGNATURE; + +/** Number of interlacing passes */ +static uint8_t png_interlace_passes[] = { +	[PNG_INTERLACE_NONE] = 1, +	[PNG_INTERLACE_ADAM7] = 7, +}; + +/** + * Transcribe PNG chunk type name (for debugging) + * + * @v type		Chunk type + * @ret name		Chunk type name + */ +static const char * png_type_name ( uint32_t type ) { +	static union { +		uint32_t type; +		char name[ sizeof ( uint32_t ) + 1 /* NUL */ ]; +	} u; + +	u.type = type; +	return u.name; +} + +/** + * Calculate PNG interlace pass parameters + * + * @v png		PNG context + * @v pass		Pass number (0=first pass) + * @v interlace		Interlace pass to fill in + */ +static void png_interlace ( struct png_context *png, unsigned int pass, +			    struct png_interlace *interlace ) { +	unsigned int grid_width_log2; +	unsigned int grid_height_log2; +	unsigned int x_indent; +	unsigned int y_indent; +	unsigned int x_stride_log2; +	unsigned int y_stride_log2; +	unsigned int x_stride; +	unsigned int y_stride; +	unsigned int width; +	unsigned int height; + +	/* Sanity check */ +	assert ( png->passes > 0 ); + +	/* Store pass number */ +	interlace->pass = pass; + +	/* Calculate interlace grid dimensions */ +	grid_width_log2 = ( png->passes / 2 ); +	grid_height_log2 = ( ( png->passes - 1 ) / 2 ); + +	/* Calculate starting indents */ +	interlace->x_indent = x_indent = +		( ( pass & 1 ) ? +		  ( 1 << ( grid_width_log2 - ( pass / 2 ) - 1 ) ) : 0 ); +	interlace->y_indent = y_indent = +		( ( pass && ! ( pass & 1 ) ) ? +		  ( 1 << ( grid_height_log2 - ( ( pass - 1 ) / 2 ) - 1 ) ) : 0); + +	/* Calculate strides */ +	x_stride_log2 = ( grid_width_log2 - ( pass / 2 ) ); +	y_stride_log2 = +		( grid_height_log2 - ( pass ? ( ( pass - 1 ) / 2 ) : 0 ) ); +	interlace->x_stride = x_stride = ( 1 << x_stride_log2 ); +	interlace->y_stride = y_stride = ( 1 << y_stride_log2 ); + +	/* Calculate pass dimensions */ +	width = png->pixbuf->width; +	height = png->pixbuf->height; +	interlace->width = +		( ( width - x_indent + x_stride - 1 ) >> x_stride_log2 ); +	interlace->height = +		( ( height - y_indent + y_stride - 1 ) >> y_stride_log2 ); +} + +/** + * Calculate PNG pixel length + * + * @v png		PNG context + * @ret pixel_len	Pixel length + */ +static unsigned int png_pixel_len ( struct png_context *png ) { + +	return ( ( ( png->channels * png->depth ) + 7 ) / 8 ); +} + +/** + * Calculate PNG scanline length + * + * @v png		PNG context + * @v interlace		Interlace pass + * @ret scanline_len	Scanline length (including filter byte) + */ +static size_t png_scanline_len ( struct png_context *png, +				 struct png_interlace *interlace ) { + +	return ( 1 /* Filter byte */ + +		 ( ( interlace->width * png->channels * png->depth ) + 7 ) / 8); +} + +/** + * Handle PNG image header chunk + * + * @v image		PNG image + * @v png		PNG context + * @v len		Chunk length + * @ret rc		Return status code + */ +static int png_image_header ( struct image *image, struct png_context *png, +			      size_t len ) { +	struct png_image_header ihdr; +	struct png_interlace interlace; +	unsigned int pass; + +	/* Sanity check */ +	if ( len != sizeof ( ihdr ) ) { +		DBGC ( image, "PNG %s invalid IHDR length %zd\n", +		       image->name, len ); +		return -EINVAL; +	} +	if ( png->pixbuf ) { +		DBGC ( image, "PNG %s duplicate IHDR\n", image->name ); +		return -EINVAL; +	} + +	/* Extract image header */ +	copy_from_user ( &ihdr, image->data, png->offset, len ); +	DBGC ( image, "PNG %s %dx%d depth %d type %d compression %d filter %d " +	       "interlace %d\n", image->name, ntohl ( ihdr.width ), +	       ntohl ( ihdr.height ), ihdr.depth, ihdr.colour_type, +	       ihdr.compression, ihdr.filter, ihdr.interlace ); + +	/* Sanity checks */ +	if ( ihdr.compression >= PNG_COMPRESSION_UNKNOWN ) { +		DBGC ( image, "PNG %s unknown compression method %d\n", +		       image->name, ihdr.compression ); +		return -ENOTSUP; +	} +	if ( ihdr.filter >= PNG_FILTER_UNKNOWN ) { +		DBGC ( image, "PNG %s unknown filter method %d\n", +		       image->name, ihdr.filter ); +		return -ENOTSUP; +	} +	if ( ihdr.interlace >= PNG_INTERLACE_UNKNOWN ) { +		DBGC ( image, "PNG %s unknown interlace method %d\n", +		       image->name, ihdr.interlace ); +		return -ENOTSUP; +	} + +	/* Allocate pixel buffer */ +	png->pixbuf = alloc_pixbuf ( ntohl ( ihdr.width ), +				     ntohl ( ihdr.height ) ); +	if ( ! png->pixbuf ) { +		DBGC ( image, "PNG %s could not allocate pixel buffer\n", +		       image->name ); +		return -ENOMEM; +	} + +	/* Extract bit depth */ +	png->depth = ihdr.depth; +	if ( ( png->depth == 0 ) || +	     ( ( png->depth & ( png->depth - 1 ) ) != 0 ) ) { +		DBGC ( image, "PNG %s invalid depth %d\n", +		       image->name, png->depth ); +		return -EINVAL; +	} + +	/* Calculate number of channels */ +	png->colour_type = ihdr.colour_type; +	png->channels = 1; +	if ( ! ( ihdr.colour_type & PNG_COLOUR_TYPE_PALETTE ) ) { +		if ( ihdr.colour_type & PNG_COLOUR_TYPE_RGB ) +			png->channels += 2; +		if ( ihdr.colour_type & PNG_COLOUR_TYPE_ALPHA ) +			png->channels += 1; +	} + +	/* Calculate number of interlace passes */ +	png->passes = png_interlace_passes[ihdr.interlace]; + +	/* Calculate length of raw data buffer */ +	for ( pass = 0 ; pass < png->passes ; pass++ ) { +		png_interlace ( png, pass, &interlace ); +		if ( interlace.width == 0 ) +			continue; +		png->raw.len += ( interlace.height * +				  png_scanline_len ( png, &interlace ) ); +	} + +	/* Allocate raw data buffer */ +	png->raw.data = umalloc ( png->raw.len ); +	if ( ! png->raw.data ) { +		DBGC ( image, "PNG %s could not allocate data buffer\n", +		       image->name ); +		return -ENOMEM; +	} + +	return 0; +} + +/** + * Handle PNG palette chunk + * + * @v image		PNG image + * @v png		PNG context + * @v len		Chunk length + * @ret rc		Return status code + */ +static int png_palette ( struct image *image, struct png_context *png, +			 size_t len ) { +	size_t offset = png->offset; +	struct png_palette_entry palette; +	unsigned int i; + +	/* Populate palette */ +	for ( i = 0 ; i < ( sizeof ( png->palette ) / +			    sizeof ( png->palette[0] ) ) ; i++ ) { + +		/* Stop when we run out of palette data */ +		if ( len < sizeof ( palette ) ) +			break; + +		/* Extract palette entry */ +		copy_from_user ( &palette, image->data, offset, +				 sizeof ( palette ) ); +		png->palette[i] = ( ( palette.red << 16 ) | +				    ( palette.green << 8 ) | +				    ( palette.blue << 0 ) ); +		DBGC2 ( image, "PNG %s palette entry %d is %#06x\n", +			image->name, i, png->palette[i] ); + +		/* Move to next entry */ +		offset += sizeof ( palette ); +		len -= sizeof ( palette ); +	} + +	return 0; +} + +/** + * Handle PNG image data chunk + * + * @v image		PNG image + * @v png		PNG context + * @v len		Chunk length + * @ret rc		Return status code + */ +static int png_image_data ( struct image *image, struct png_context *png, +			    size_t len ) { +	struct deflate_chunk in; +	int rc; + +	/* Deflate this chunk */ +	deflate_chunk_init ( &in, image->data, png->offset, +			     ( png->offset + len ) ); +	if ( ( rc = deflate_inflate ( &png->deflate, &in, &png->raw ) ) != 0 ) { +		DBGC ( image, "PNG %s could not decompress: %s\n", +		       image->name, strerror ( rc ) ); +		return rc; +	} + +	return 0; +} + +/** + * Unfilter byte using the "None" filter + * + * @v current		Filtered current byte + * @v left		Unfiltered left byte + * @v above		Unfiltered above byte + * @v above_left	Unfiltered above-left byte + * @ret current		Unfiltered current byte + */ +static unsigned int png_unfilter_none ( unsigned int current, +					unsigned int left __unused, +					unsigned int above __unused, +					unsigned int above_left __unused ) { + +	return current; +} + +/** + * Unfilter byte using the "Sub" filter + * + * @v current		Filtered current byte + * @v left		Unfiltered left byte + * @v above		Unfiltered above byte + * @v above_left	Unfiltered above-left byte + * @ret current		Unfiltered current byte + */ +static unsigned int png_unfilter_sub ( unsigned int current, +				       unsigned int left, +				       unsigned int above __unused, +				       unsigned int above_left __unused ) { + +	return ( current + left ); +} + +/** + * Unfilter byte using the "Up" filter + * + * @v current		Filtered current byte + * @v left		Unfiltered left byte + * @v above		Unfiltered above byte + * @v above_left	Unfiltered above-left byte + * @ret current		Unfiltered current byte + */ +static unsigned int png_unfilter_up ( unsigned int current, +				      unsigned int left __unused, +				      unsigned int above, +				      unsigned int above_left __unused ) { + +	return ( current + above ); +} + +/** + * Unfilter byte using the "Average" filter + * + * @v current		Filtered current byte + * @v left		Unfiltered left byte + * @v above		Unfiltered above byte + * @v above_left	Unfiltered above-left byte + * @ret current		Unfiltered current byte + */ +static unsigned int png_unfilter_average ( unsigned int current, +					   unsigned int left, +					   unsigned int above, +					   unsigned int above_left __unused ) { + +	return ( current + ( ( above + left ) >> 1 ) ); +} + +/** + * Paeth predictor function (defined in RFC 2083) + * + * @v a			Pixel A + * @v b			Pixel B + * @v c			Pixel C + * @ret predictor	Predictor pixel + */ +static unsigned int png_paeth_predictor ( unsigned int a, unsigned int b, +					  unsigned int c ) { +	unsigned int p; +	unsigned int pa; +	unsigned int pb; +	unsigned int pc; + +	/* Algorithm as defined in RFC 2083 section 6.6 */ +	p = ( a + b - c ); +	pa = abs ( p - a ); +	pb = abs ( p - b ); +	pc = abs ( p - c ); +	if ( ( pa <= pb ) && ( pa <= pc ) ) { +		return a; +	} else if ( pb <= pc ) { +		return b; +	} else { +		return c; +	} +} + +/** + * Unfilter byte using the "Paeth" filter + * + * @v current		Filtered current byte + * @v above_left	Unfiltered above-left byte + * @v above		Unfiltered above byte + * @v left		Unfiltered left byte + * @ret current		Unfiltered current byte + */ +static unsigned int png_unfilter_paeth ( unsigned int current, +					 unsigned int left, +					 unsigned int above, +					 unsigned int above_left ) { + +	return ( current + png_paeth_predictor ( left, above, above_left ) ); +} + +/** A PNG filter */ +struct png_filter { +	/** +	 * Unfilter byte +	 * +	 * @v current		Filtered current byte +	 * @v left		Unfiltered left byte +	 * @v above		Unfiltered above byte +	 * @v above_left	Unfiltered above-left byte +	 * @ret current		Unfiltered current byte +	 */ +	unsigned int ( * unfilter ) ( unsigned int current, +				      unsigned int left, +				      unsigned int above, +				      unsigned int above_left ); +}; + +/** PNG filter types */ +static struct png_filter png_filters[] = { +	[PNG_FILTER_BASIC_NONE] = { png_unfilter_none }, +	[PNG_FILTER_BASIC_SUB] = { png_unfilter_sub }, +	[PNG_FILTER_BASIC_UP] = { png_unfilter_up }, +	[PNG_FILTER_BASIC_AVERAGE] = { png_unfilter_average }, +	[PNG_FILTER_BASIC_PAETH] = { png_unfilter_paeth }, +}; + +/** + * Unfilter one interlace pass of PNG raw data + * + * @v image		PNG image + * @v png		PNG context + * @v interlace		Interlace pass + * @ret rc		Return status code + * + * This routine may assume that it is impossible to overrun the raw + * data buffer, since the size is determined by the image dimensions. + */ +static int png_unfilter_pass ( struct image *image, struct png_context *png, +			       struct png_interlace *interlace ) { +	size_t offset = png->raw.offset; +	size_t pixel_len = png_pixel_len ( png ); +	size_t scanline_len = png_scanline_len ( png, interlace ); +	struct png_filter *filter; +	unsigned int scanline; +	unsigned int byte; +	uint8_t filter_type; +	uint8_t left; +	uint8_t above; +	uint8_t above_left; +	uint8_t current; + +	/* On the first scanline of a pass, above bytes are assumed to +	 * be zero. +	 */ +	above = 0; + +	/* Iterate over each scanline in turn */ +	for ( scanline = 0 ; scanline < interlace->height ; scanline++ ) { + +		/* Extract filter byte and determine filter type */ +		copy_from_user ( &filter_type, png->raw.data, offset++, +				 sizeof ( filter_type ) ); +		if ( filter_type >= ( sizeof ( png_filters ) / +				      sizeof ( png_filters[0] ) ) ) { +			DBGC ( image, "PNG %s unknown filter type %d\n", +			       image->name, filter_type ); +			return -ENOTSUP; +		} +		filter = &png_filters[filter_type]; +		assert ( filter->unfilter != NULL ); +		DBGC2 ( image, "PNG %s pass %d scanline %d filter type %d\n", +			image->name, interlace->pass, scanline, filter_type ); + +		/* At the start of a line, both above-left and left +		 * bytes are taken to be zero. +		 */ +		left = 0; +		above_left = 0; + +		/* Iterate over each byte (not pixel) in turn */ +		for ( byte = 0 ; byte < ( scanline_len - 1 ) ; byte++ ) { + +			/* Extract predictor bytes, if applicable */ +			if ( byte >= pixel_len ) { +				copy_from_user ( &left, png->raw.data, +						 ( offset - pixel_len ), +						 sizeof ( left ) ); +			} +			if ( scanline > 0 ) { +				copy_from_user ( &above, png->raw.data, +						 ( offset - scanline_len ), +						 sizeof ( above ) ); +			} +			if ( ( scanline > 0 ) && ( byte >= pixel_len ) ) { +				copy_from_user ( &above_left, png->raw.data, +						 ( offset - scanline_len - +						   pixel_len ), +						 sizeof ( above_left ) ); +			} + +			/* Unfilter current byte */ +			copy_from_user ( ¤t, png->raw.data, +					 offset, sizeof ( current ) ); +			current = filter->unfilter ( current, left, above, +						     above_left ); +			copy_to_user ( png->raw.data, offset++, +				       ¤t, sizeof ( current ) ); +		} +	} + +	/* Update offset */ +	png->raw.offset = offset; + +	return 0; +} + +/** + * Unfilter PNG raw data + * + * @v image		PNG image + * @v png		PNG context + * @ret rc		Return status code + * + * This routine may assume that it is impossible to overrun the raw + * data buffer, since the size is determined by the image dimensions. + */ +static int png_unfilter ( struct image *image, struct png_context *png ) { +	struct png_interlace interlace; +	unsigned int pass; +	int rc; + +	/* Process each interlace pass */ +	png->raw.offset = 0; +	for ( pass = 0 ; pass < png->passes ; pass++ ) { + +		/* Calculate interlace pass parameters */ +		png_interlace ( png, pass, &interlace ); + +		/* Skip zero-width rows (which have no filter bytes) */ +		if ( interlace.width == 0 ) +			continue; + +		/* Unfilter this pass */ +		if ( ( rc = png_unfilter_pass ( image, png, +						&interlace ) ) != 0 ) +			return rc; +	} +	assert ( png->raw.offset == png->raw.len ); + +	return 0; +} + +/** + * Calculate PNG pixel component value + * + * @v raw		Raw component value + * @v alpha		Alpha value + * @v max		Maximum raw/alpha value + * @ret value		Component value in range 0-255 + */ +static inline unsigned int png_pixel ( unsigned int raw, unsigned int alpha, +				       unsigned int max ) { + +	/* The basic calculation is 255*(raw/max)*(value/max).  We use +	 * fixed-point arithmetic (scaling up to the maximum range for +	 * a 32-bit integer), in order to get the same results for +	 * alpha blending as the test cases (produced using +	 * ImageMagick). +	 */ +	return ( ( ( ( ( 0xff00 * raw * alpha ) / max ) / max ) + 0x80 ) >> 8 ); +} + +/** + * Fill one interlace pass of PNG pixels + * + * @v image		PNG image + * @v png		PNG context + * @v interlace		Interlace pass + * + * This routine may assume that it is impossible to overrun either the + * raw data buffer or the pixel buffer, since the sizes of both are + * determined by the image dimensions. + */ +static void png_pixels_pass ( struct image *image, +			      struct png_context *png, +			      struct png_interlace *interlace ) { +	size_t raw_offset = png->raw.offset; +	uint8_t channel[png->channels]; +	int is_indexed = ( png->colour_type & PNG_COLOUR_TYPE_PALETTE ); +	int is_rgb = ( png->colour_type & PNG_COLOUR_TYPE_RGB ); +	int has_alpha = ( png->colour_type & PNG_COLOUR_TYPE_ALPHA ); +	size_t pixbuf_y_offset; +	size_t pixbuf_offset; +	size_t pixbuf_x_stride; +	size_t pixbuf_y_stride; +	size_t raw_stride; +	unsigned int y; +	unsigned int x; +	unsigned int c; +	unsigned int bits; +	unsigned int depth; +	unsigned int max; +	unsigned int alpha; +	unsigned int raw; +	unsigned int value; +	uint8_t current = 0; +	uint32_t pixel; + +	/* We only ever use the top byte of 16-bit pixels.  Model this +	 * as a bit depth of 8 with a stride of more than one. +	 */ +	depth = png->depth; +	raw_stride = ( ( depth + 7 ) / 8 ); +	if ( depth > 8 ) +		depth = 8; +	max = ( ( 1 << depth ) - 1 ); + +	/* Calculate pixel buffer offset and strides */ +	pixbuf_y_offset = ( ( ( interlace->y_indent * png->pixbuf->width ) + +			      interlace->x_indent ) * sizeof ( pixel ) ); +	pixbuf_x_stride = ( interlace->x_stride * sizeof ( pixel ) ); +	pixbuf_y_stride = ( interlace->y_stride * png->pixbuf->width * +			    sizeof ( pixel ) ); +	DBGC2 ( image, "PNG %s pass %d %dx%d at (%d,%d) stride (%d,%d)\n", +		image->name, interlace->pass, interlace->width, +		interlace->height, interlace->x_indent, interlace->y_indent, +		interlace->x_stride, interlace->y_stride ); + +	/* Iterate over each scanline in turn */ +	for ( y = 0 ; y < interlace->height ; y++ ) { + +		/* Skip filter byte */ +		raw_offset++; + +		/* Iterate over each pixel in turn */ +		bits = depth; +		pixbuf_offset = pixbuf_y_offset; +		for ( x = 0 ; x < interlace->width ; x++ ) { + +			/* Extract sample value */ +			for ( c = 0 ; c < png->channels ; c++ ) { + +				/* Get sample value into high bits of current */ +				current <<= depth; +				bits -= depth; +				if ( ! bits ) { +					copy_from_user ( ¤t, +							 png->raw.data, +							 raw_offset, +							 sizeof ( current ) ); +					raw_offset += raw_stride; +					bits = 8; +				} + +				/* Extract sample value */ +				channel[c] = ( current >> ( 8 - depth ) ); +			} + +			/* Convert to native pixel format */ +			if ( is_indexed ) { + +				/* Indexed */ +				pixel = png->palette[channel[0]]; + +			} else { + +				/* Determine alpha value */ +				alpha = ( has_alpha ? +					  channel[ png->channels - 1 ] : max ); + +				/* Convert to RGB value */ +				pixel = 0; +				for ( c = 0 ; c < 3 ; c++ ) { +					raw = channel[ is_rgb ? c : 0 ]; +					value = png_pixel ( raw, alpha, max ); +					assert ( value <= 255 ); +					pixel = ( ( pixel << 8 ) | value ); +				} +			} + +			/* Store pixel */ +			copy_to_user ( png->pixbuf->data, pixbuf_offset, +				       &pixel, sizeof ( pixel ) ); +			pixbuf_offset += pixbuf_x_stride; +		} + +		/* Move to next output row */ +		pixbuf_y_offset += pixbuf_y_stride; +	} + +	/* Update offset */ +	png->raw.offset = raw_offset; +} + +/** + * Fill PNG pixels + * + * @v image		PNG image + * @v png		PNG context + * + * This routine may assume that it is impossible to overrun either the + * raw data buffer or the pixel buffer, since the sizes of both are + * determined by the image dimensions. + */ +static void png_pixels ( struct image *image, struct png_context *png ) { +	struct png_interlace interlace; +	unsigned int pass; + +	/* Process each interlace pass */ +	png->raw.offset = 0; +	for ( pass = 0 ; pass < png->passes ; pass++ ) { + +		/* Calculate interlace pass parameters */ +		png_interlace ( png, pass, &interlace ); + +		/* Skip zero-width rows (which have no filter bytes) */ +		if ( interlace.width == 0 ) +			continue; + +		/* Unfilter this pass */ +		png_pixels_pass ( image, png, &interlace ); +	} +	assert ( png->raw.offset == png->raw.len ); +} + +/** + * Handle PNG image end chunk + * + * @v image		PNG image + * @v png		PNG context + * @v len		Chunk length + * @ret rc		Return status code + */ +static int png_image_end ( struct image *image, struct png_context *png, +			   size_t len ) { +	int rc; + +	/* Sanity checks */ +	if ( len != 0 ) { +		DBGC ( image, "PNG %s invalid IEND length %zd\n", +		       image->name, len ); +		return -EINVAL; +	} +	if ( ! png->pixbuf ) { +		DBGC ( image, "PNG %s missing pixel buffer (no IHDR?)\n", +		       image->name ); +		return -EINVAL; +	} +	if ( ! deflate_finished ( &png->deflate ) ) { +		DBGC ( image, "PNG %s decompression not complete\n", +		       image->name ); +		return -EINVAL; +	} +	if ( png->raw.offset != png->raw.len ) { +		DBGC ( image, "PNG %s incorrect decompressed length (expected " +		       "%zd, got %zd)\n", image->name, png->raw.len, +		       png->raw.offset ); +		return -EINVAL; +	} + +	/* Unfilter raw data */ +	if ( ( rc = png_unfilter ( image, png ) ) != 0 ) +		return rc; + +	/* Fill pixel buffer */ +	png_pixels ( image, png ); + +	return 0; +} + +/** A PNG chunk handler */ +struct png_chunk_handler { +	/** Chunk type */ +	uint32_t type; +	/** +	 * Handle chunk +	 * +	 * @v image		PNG image +	 * @v png		PNG context +	 * @v len		Chunk length +	 * @ret rc		Return status code +	 */ +	int ( * handle ) ( struct image *image, struct png_context *png, +			   size_t len ); +}; + +/** PNG chunk handlers */ +static struct png_chunk_handler png_chunk_handlers[] = { +	{ htonl ( PNG_TYPE_IHDR ), png_image_header }, +	{ htonl ( PNG_TYPE_PLTE ), png_palette }, +	{ htonl ( PNG_TYPE_IDAT ), png_image_data }, +	{ htonl ( PNG_TYPE_IEND ), png_image_end }, +}; + +/** + * Handle PNG chunk + * + * @v image		PNG image + * @v png		PNG context + * @v type		Chunk type + * @v len		Chunk length + * @ret rc		Return status code + */ +static int png_chunk ( struct image *image, struct png_context *png, +		       uint32_t type, size_t len ) { +	struct png_chunk_handler *handler; +	unsigned int i; + +	DBGC ( image, "PNG %s chunk type %s offset %zd length %zd\n", +	       image->name, png_type_name ( type ), png->offset, len ); + +	/* Handle according to chunk type */ +	for ( i = 0 ; i < ( sizeof ( png_chunk_handlers ) / +			    sizeof ( png_chunk_handlers[0] ) ) ; i++ ) { +		handler = &png_chunk_handlers[i]; +		if ( handler->type == type ) +			return handler->handle ( image, png, len ); +	} + +	/* Fail if unknown chunk type is critical */ +	if ( ! ( type & htonl ( PNG_CHUNK_ANCILLARY ) ) ) { +		DBGC ( image, "PNG %s unknown critical chunk type %s\n", +		       image->name, png_type_name ( type ) ); +		return -ENOTSUP; +	} + +	/* Ignore non-critical unknown chunk types */ +	return 0; +} + +/** + * Convert PNG image to pixel buffer + * + * @v image		PNG image + * @v pixbuf		Pixel buffer to fill in + * @ret rc		Return status code + */ +static int png_pixbuf ( struct image *image, struct pixel_buffer **pixbuf ) { +	struct png_context *png; +	struct png_chunk_header header; +	struct png_chunk_footer footer; +	size_t remaining; +	size_t chunk_len; +	int rc; + +	/* Allocate and initialise context */ +	png = zalloc ( sizeof ( *png ) ); +	if ( ! png ) { +		rc = -ENOMEM; +		goto err_alloc; +	} +	png->offset = sizeof ( struct png_signature ); +	deflate_init ( &png->deflate, DEFLATE_ZLIB ); + +	/* Process chunks */ +	do { + +		/* Extract chunk header */ +		remaining = ( image->len - png->offset ); +		if ( remaining < sizeof ( header ) ) { +			DBGC ( image, "PNG %s truncated chunk header at offset " +			       "%zd\n", image->name, png->offset ); +			rc = -EINVAL; +			goto err_truncated; +		} +		copy_from_user ( &header, image->data, png->offset, +				 sizeof ( header ) ); +		png->offset += sizeof ( header ); + +		/* Validate chunk length */ +		chunk_len = ntohl ( header.len ); +		if ( remaining < ( sizeof ( header ) + chunk_len + +				   sizeof ( footer ) ) ) { +			DBGC ( image, "PNG %s truncated chunk data/footer at " +			       "offset %zd\n", image->name, png->offset ); +			rc = -EINVAL; +			goto err_truncated; +		} + +		/* Handle chunk */ +		if ( ( rc = png_chunk ( image, png, header.type, +					chunk_len ) ) != 0 ) +			goto err_chunk; + +		/* Move to next chunk */ +		png->offset += ( chunk_len + sizeof ( footer ) ); + +	} while ( png->offset < image->len ); + +	/* Check that we finished with an IEND chunk */ +	if ( header.type != htonl ( PNG_TYPE_IEND ) ) { +		DBGC ( image, "PNG %s did not finish with IEND\n", +		       image->name ); +		rc = -EINVAL; +		goto err_iend; +	} + +	/* Return pixel buffer */ +	*pixbuf = pixbuf_get ( png->pixbuf ); + +	/* Success */ +	rc = 0; + + err_iend: + err_chunk: + err_truncated: +	pixbuf_put ( png->pixbuf ); +	ufree ( png->raw.data ); +	free ( png ); + err_alloc: +	return rc; +} + +/** + * Probe PNG image + * + * @v image		PNG image + * @ret rc		Return status code + */ +static int png_probe ( struct image *image ) { +	struct png_signature signature; + +	/* Sanity check */ +	if ( image->len < sizeof ( signature ) ) { +		DBGC ( image, "PNG %s is too short\n", image->name ); +		return -ENOEXEC; +	} + +	/* Check signature */ +	copy_from_user ( &signature, image->data, 0, sizeof ( signature ) ); +	if ( memcmp ( &signature, &png_signature, sizeof ( signature ) ) != 0 ){ +		DBGC ( image, "PNG %s has invalid signature\n", image->name ); +		return -ENOEXEC; +	} + +	return 0; +} + +/** PNG image type */ +struct image_type png_image_type __image_type ( PROBE_NORMAL ) = { +	.name = "PNG", +	.probe = png_probe, +	.pixbuf = png_pixbuf, +}; diff --git a/roms/ipxe/src/image/pnm.c b/roms/ipxe/src/image/pnm.c new file mode 100644 index 00000000..af9e571a --- /dev/null +++ b/roms/ipxe/src/image/pnm.c @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** @file + * + * Portable anymap format (PNM) + * + */ + +#include <stdlib.h> +#include <errno.h> +#include <ctype.h> +#include <ipxe/image.h> +#include <ipxe/pixbuf.h> +#include <ipxe/pnm.h> + +/** + * Extract PNM ASCII value + * + * @v image		PNM image + * @v pnm		PNM context + * @ret value		Value, or negative error + */ +static int pnm_ascii ( struct image *image, struct pnm_context *pnm ) { +	char buf[ pnm->ascii_len + 1 /* NUL */ ]; +	char *endp; +	size_t len; +	int value; +	int in_comment = 0; + +	/* Skip any leading whitespace and comments */ +	for ( ; pnm->offset < image->len ; pnm->offset++ ) { +		copy_from_user ( &buf[0], image->data, pnm->offset, +				 sizeof ( buf[0] ) ); +		if ( in_comment ) { +			if ( buf[0] == '\n' ) +				in_comment = 0; +		} else { +			if ( buf[0] == '#' ) { +				in_comment = 1; +			} else if ( ! isspace ( buf[0] ) ) { +				break; +			} +		} +	} + +	/* Fail if no value is present */ +	len = ( image->len - pnm->offset ); +	if ( len == 0 ) { +		DBGC ( image, "PNM %s ran out of ASCII data\n", image->name ); +		return -EINVAL; +	} + +	/* Copy ASCII value to buffer and ensure string is NUL-terminated */ +	if ( len > ( sizeof ( buf ) - 1 /* NUL */ ) ) +		len = ( sizeof ( buf ) - 1 /* NUL */ ); +	copy_from_user ( buf, image->data, pnm->offset, len ); +	buf[len] = '\0'; + +	/* Parse value and update offset */ +	value = strtoul ( buf, &endp, 0 ); +	pnm->offset += ( endp - buf ); + +	/* Check and skip terminating whitespace character, if present */ +	if ( ( pnm->offset != image->len ) && ( *endp != '\0' ) ) { +		if ( ! isspace ( *endp ) ) { +			DBGC ( image, "PNM %s invalid ASCII integer\n", +			       image->name ); +			return -EINVAL; +		} +		pnm->offset++; +	} + +	return value; +} + +/** + * Extract PNM binary value + * + * @v image		PNM image + * @v pnm		PNM context + * @ret value		Value, or negative error + */ +static int pnm_binary ( struct image *image, struct pnm_context *pnm ) { +	uint8_t value; + +	/* Sanity check */ +	if ( pnm->offset == image->len ) { +		DBGC ( image, "PNM %s ran out of binary data\n", +		       image->name ); +		return -EINVAL; +	} + +	/* Extract value */ +	copy_from_user ( &value, image->data, pnm->offset, sizeof ( value ) ); +	pnm->offset++; + +	return value; +} + +/** + * Scale PNM scalar value + * + * @v image		PNM image + * @v pnm		PNM context + * @v value		Raw value + * @ret value		Scaled value (in range 0-255) + */ +static int pnm_scale ( struct image *image, struct pnm_context *pnm, +		       unsigned int value ) { + +	if ( value > pnm->max ) { +		DBGC ( image, "PNM %s has out-of-range value %d (max %d)\n", +		       image->name, value, pnm->max ); +		return -EINVAL; +	} +	return ( ( 255 * value ) / pnm->max ); +} + +/** + * Convert PNM bitmap composite value to RGB + * + * @v composite		Composite value + * @v index		Pixel index within this composite value + * @ret rgb		24-bit RGB value + */ +static uint32_t pnm_bitmap ( uint32_t composite, unsigned int index ) { + +	/* Composite value is an 8-bit bitmask */ +	return ( ( ( composite << index ) & 0x80 ) ? 0x000000 : 0xffffff ); +} + +/** + * Convert PNM greymap composite value to RGB + * + * @v composite		Composite value + * @v index		Pixel index within this composite value + * @ret rgb		24-bit RGB value + */ +static uint32_t pnm_greymap ( uint32_t composite, unsigned int index __unused ){ + +	/* Composite value is an 8-bit greyscale value */ +	return ( ( composite << 16 ) | ( composite << 8 ) | composite ); +} + +/** + * Convert PNM pixmap composite value to RGB + * + * @v composite		Composite value + * @v index		Pixel index within this composite value + * @ret rgb		24-bit RGB value + */ +static uint32_t pnm_pixmap ( uint32_t composite, unsigned int index __unused ) { + +	/* Composite value is already an RGB value */ +	return composite; +} + +/** + * Extract PNM pixel data + * + * @v image		PNM image + * @v pnm		PNM context + * @v pixbuf		Pixel buffer + * @ret rc		Return status code + */ +static int pnm_data ( struct image *image, struct pnm_context *pnm, +		      struct pixel_buffer *pixbuf ) { +	struct pnm_type *type = pnm->type; +	size_t offset = 0; +	unsigned int xpos = 0; +	int scalar; +	uint32_t composite; +	uint32_t rgb; +	unsigned int i; + +	/* Fill pixel buffer */ +	while ( offset < pixbuf->len ) { + +		/* Extract a scaled composite scalar value from the file */ +		composite = 0; +		for ( i = 0 ; i < type->depth ; i++ ) { +			scalar = type->scalar ( image, pnm ); +			if ( scalar < 0 ) +				return scalar; +			scalar = pnm_scale ( image, pnm, scalar ); +			if ( scalar < 0 ) +				return scalar; +			composite = ( ( composite << 8 ) | scalar ); +		} + +		/* Extract 24-bit RGB values from composite value */ +		for ( i = 0 ; i < type->packing ; i++ ) { +			if ( offset >= pixbuf->len ) { +				DBGC ( image, "PNM %s has too many pixels\n", +				       image->name ); +				return -EINVAL; +			} +			rgb = type->rgb ( composite, i ); +			copy_to_user ( pixbuf->data, offset, &rgb, +				       sizeof ( rgb ) ); +			offset += sizeof ( rgb ); +			if ( ++xpos == pixbuf->width ) { +				xpos = 0; +				break; +			} +		} +	} + +	return 0; +} + +/** PNM image types */ +static struct pnm_type pnm_types[] = { +	{ +		.type = '1', +		.depth = 1, +		.packing = 1, +		.flags = PNM_BITMAP, +		.scalar = pnm_ascii, +		.rgb = pnm_bitmap, +	}, +	{ +		.type = '2', +		.depth = 1, +		.packing = 1, +		.scalar = pnm_ascii, +		.rgb = pnm_greymap, +	}, +	{ +		.type = '3', +		.depth = 3, +		.packing = 1, +		.scalar = pnm_ascii, +		.rgb = pnm_pixmap, +	}, +	{ +		.type = '4', +		.depth = 1, +		.packing = 8, +		.flags = PNM_BITMAP, +		.scalar = pnm_binary, +		.rgb = pnm_bitmap, +	}, +	{ +		.type = '5', +		.depth = 1, +		.packing = 1, +		.scalar = pnm_binary, +		.rgb = pnm_greymap, +	}, +	{ +		.type = '6', +		.depth = 3, +		.packing = 1, +		.scalar = pnm_binary, +		.rgb = pnm_pixmap, +	}, +}; + +/** + * Determine PNM image type + * + * @v image		PNM image + * @ret type		PNM image type, or NULL if not found + */ +static struct pnm_type * pnm_type ( struct image *image ) { +	struct pnm_signature signature; +	struct pnm_type *type; +	unsigned int i; + +	/* Extract signature */ +	assert ( image->len >= sizeof ( signature ) ); +	copy_from_user ( &signature, image->data, 0, sizeof ( signature ) ); + +	/* Check for supported types */ +	for ( i = 0 ; i < ( sizeof ( pnm_types ) / +			    sizeof ( pnm_types[0] ) ) ; i++ ) { +		type = &pnm_types[i]; +		if ( type->type == signature.type ) +			return type; +	} +	return NULL; +} + +/** + * Convert PNM image to pixel buffer + * + * @v image		PNM image + * @v pixbuf		Pixel buffer to fill in + * @ret rc		Return status code + */ +static int pnm_pixbuf ( struct image *image, struct pixel_buffer **pixbuf ) { +	struct pnm_context pnm; +	int width; +	int height; +	int max; +	int rc; + +	/* Initialise PNM context */ +	pnm.type = pnm_type ( image ); +	if ( ! pnm.type ) { +		rc = -ENOTSUP; +		goto err_type; +	} +	pnm.offset = sizeof ( struct pnm_signature ); +	pnm.ascii_len = PNM_ASCII_LEN; + +	/* Extract width */ +	if ( ( width = pnm_ascii ( image, &pnm ) ) < 0 ) { +		rc = width; +		goto err_width; +	} + +	/* Extract height */ +	if ( ( height = pnm_ascii ( image, &pnm ) ) < 0 ) { +		rc = height; +		goto err_height; +	} + +	/* Extract maximum scalar value, if not predefined */ +	if ( pnm.type->flags & PNM_BITMAP ) { +		pnm.max = ( ( 1 << pnm.type->packing ) - 1 ); +		pnm.ascii_len = 1; +	} else { +		if ( ( max = pnm_ascii ( image, &pnm ) ) < 0 ) { +			rc = max; +			goto err_max; +		} +		pnm.max = max; +	} +	if ( pnm.max == 0 ) { +		DBGC ( image, "PNM %s has invalid maximum value 0\n", +		       image->name ); +		rc = -EINVAL; +		goto err_max; +	} +	DBGC ( image, "PNM %s is type %c width %d height %d max %d\n", +	       image->name, pnm.type->type, width, height, pnm.max ); + +	/* Allocate pixel buffer */ +	*pixbuf = alloc_pixbuf ( width, height ); +	if ( ! *pixbuf ) { +		rc = -ENOMEM; +		goto err_alloc_pixbuf; +	} + +	/* Extract pixel data */ +	if ( ( rc = pnm_data ( image, &pnm, *pixbuf ) ) != 0 ) +		goto err_data; + +	return 0; + + err_data: +	pixbuf_put ( *pixbuf ); + err_alloc_pixbuf: + err_max: + err_height: + err_width: + err_type: +	return rc; +} + +/** + * Probe PNM image + * + * @v image		PNM image + * @ret rc		Return status code + */ +static int pnm_probe ( struct image *image ) { +	struct pnm_signature signature; + +	/* Sanity check */ +	if ( image->len < sizeof ( signature ) ) { +		DBGC ( image, "PNM %s is too short\n", image->name ); +		return -ENOEXEC; +	} + +	/* Check signature */ +	copy_from_user ( &signature, image->data, 0, sizeof ( signature ) ); +	if ( ! ( ( signature.magic == PNM_MAGIC ) && +		 ( isdigit ( signature.type ) ) && +		 ( isspace ( signature.space ) ) ) ) { +		DBGC ( image, "PNM %s has invalid signature\n", image->name ); +		return -ENOEXEC; +	} +	DBGC ( image, "PNM %s is type %c\n", image->name, signature.type ); + +	return 0; +} + +/** PNM image type */ +struct image_type pnm_image_type __image_type ( PROBE_NORMAL ) = { +	.name = "PNM", +	.probe = pnm_probe, +	.pixbuf = pnm_pixbuf, +}; diff --git a/roms/ipxe/src/image/script.c b/roms/ipxe/src/image/script.c new file mode 100644 index 00000000..5328da8b --- /dev/null +++ b/roms/ipxe/src/image/script.c @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** + * @file + * + * iPXE scripts + * + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <ipxe/command.h> +#include <ipxe/parseopt.h> +#include <ipxe/image.h> +#include <ipxe/shell.h> +#include <usr/prompt.h> +#include <ipxe/script.h> + +/** Offset within current script + * + * This is a global in order to allow goto_exec() to update the + * offset. + */ +static size_t script_offset; + +/** + * Process script lines + * + * @v image		Script + * @v process_line	Line processor + * @v terminate		Termination check + * @ret rc		Return status code + */ +static int process_script ( struct image *image, +			    int ( * process_line ) ( struct image *image, +						     size_t offset, +						     const char *label, +						     const char *command ), +			    int ( * terminate ) ( int rc ) ) { +	size_t len = 0; +	char *line = NULL; +	size_t line_offset; +	char *label; +	char *command; +	off_t eol; +	size_t frag_len; +	char *tmp; +	int rc; + +	/* Initialise script and line offsets */ +	script_offset = 0; +	line_offset = 0; + +	do { + +		/* Find length of next line, excluding any terminating '\n' */ +		eol = memchr_user ( image->data, script_offset, '\n', +				    ( image->len - script_offset ) ); +		if ( eol < 0 ) +			eol = image->len; +		frag_len = ( eol - script_offset ); + +		/* Allocate buffer for line */ +		tmp = realloc ( line, ( len + frag_len + 1 /* NUL */ ) ); +		if ( ! tmp ) { +			rc = -ENOMEM; +			goto err_alloc; +		} +		line = tmp; + +		/* Copy line */ +		copy_from_user ( ( line + len ), image->data, script_offset, +				 frag_len ); +		len += frag_len; + +		/* Move to next line in script */ +		script_offset += ( frag_len + 1 ); + +		/* Strip trailing CR, if present */ +		if ( len && ( line[ len - 1 ] == '\r' ) ) +			len--; + +		/* Handle backslash continuations */ +		if ( len && ( line[ len - 1 ] == '\\' ) ) { +			len--; +			rc = -EINVAL; +			continue; +		} + +		/* Terminate line */ +		line[len] = '\0'; + +		/* Split line into (optional) label and command */ +		command = line; +		while ( isspace ( *command ) ) +			command++; +		if ( *command == ':' ) { +			label = ++command; +			while ( *command && ! isspace ( *command ) ) +				command++; +			if ( *command ) +				*(command++) = '\0'; +		} else { +			label = NULL; +		} + +		/* Process line */ +		rc = process_line ( image, line_offset, label, command ); +		if ( terminate ( rc ) ) +			goto err_process; + +		/* Free line */ +		free ( line ); +		line = NULL; +		len = 0; + +		/* Update line offset */ +		line_offset = script_offset; + +	} while ( script_offset < image->len ); + + err_process: + err_alloc: +	free ( line ); +	return rc; +} + +/** + * Terminate script processing on shell exit or command failure + * + * @v rc		Line processing status + * @ret terminate	Terminate script processing + */ +static int terminate_on_exit_or_failure ( int rc ) { + +	return ( shell_stopped ( SHELL_STOP_COMMAND_SEQUENCE ) || +		 ( rc != 0 ) ); +} + +/** + * Execute script line + * + * @v image		Script + * @v offset		Offset within script + * @v label		Label, or NULL + * @v command		Command + * @ret rc		Return status code + */ +static int script_exec_line ( struct image *image, size_t offset, +			      const char *label __unused, +			      const char *command ) { +	int rc; + +	DBGC ( image, "[%04zx] $ %s\n", offset, command ); + +	/* Execute command */ +	if ( ( rc = system ( command ) ) != 0 ) +		return rc; + +	return 0; +} + +/** + * Execute script + * + * @v image		Script + * @ret rc		Return status code + */ +static int script_exec ( struct image *image ) { +	size_t saved_offset; +	int rc; + +	/* Temporarily de-register image, so that a "boot" command +	 * doesn't throw us into an execution loop. +	 */ +	unregister_image ( image ); + +	/* Preserve state of any currently-running script */ +	saved_offset = script_offset; + +	/* Process script */ +	rc = process_script ( image, script_exec_line, +			      terminate_on_exit_or_failure ); + +	/* Restore saved state */ +	script_offset = saved_offset; + +	/* Re-register image (unless we have been replaced) */ +	if ( ! image->replacement ) +		register_image ( image ); + +	return rc; +} + +/** + * Probe script image + * + * @v image		Script + * @ret rc		Return status code + */ +static int script_probe ( struct image *image ) { +	static const char ipxe_magic[] = "#!ipxe"; +	static const char gpxe_magic[] = "#!gpxe"; +	linker_assert ( sizeof ( ipxe_magic ) == sizeof ( gpxe_magic ), +			magic_size_mismatch ); +	char test[ sizeof ( ipxe_magic ) - 1 /* NUL */ +		   + 1 /* terminating space */]; + +	/* Sanity check */ +	if ( image->len < sizeof ( test ) ) { +		DBGC ( image, "Too short to be a script\n" ); +		return -ENOEXEC; +	} + +	/* Check for magic signature */ +	copy_from_user ( test, image->data, 0, sizeof ( test ) ); +	if ( ! ( ( ( memcmp ( test, ipxe_magic, sizeof ( test ) - 1 ) == 0 ) || +		   ( memcmp ( test, gpxe_magic, sizeof ( test ) - 1 ) == 0 )) && +		 isspace ( test[ sizeof ( test ) - 1 ] ) ) ) { +		DBGC ( image, "Invalid magic signature\n" ); +		return -ENOEXEC; +	} + +	return 0; +} + +/** Script image type */ +struct image_type script_image_type __image_type ( PROBE_NORMAL ) = { +	.name = "script", +	.probe = script_probe, +	.exec = script_exec, +}; + +/** "goto" options */ +struct goto_options {}; + +/** "goto" option list */ +static struct option_descriptor goto_opts[] = {}; + +/** "goto" command descriptor */ +static struct command_descriptor goto_cmd = +	COMMAND_DESC ( struct goto_options, goto_opts, 1, 1, "<label>" ); + +/** + * Current "goto" label + * + * Valid only during goto_exec().  Consider this part of a closure. + */ +static const char *goto_label; + +/** + * Check for presence of label + * + * @v image		Script + * @v offset		Offset within script + * @v label		Label + * @v command		Command + * @ret rc		Return status code + */ +static int goto_find_label ( struct image *image, size_t offset, +			     const char *label, const char *command __unused ) { + +	/* Check label exists */ +	if ( ! label ) +		return -ENOENT; + +	/* Check label matches */ +	if ( strcmp ( goto_label, label ) != 0 ) +		return -ENOENT; + +	/* Update script offset */ +	script_offset = offset; +	DBGC ( image, "[%04zx] Gone to :%s\n", offset, label ); + +	return 0; +} + +/** + * Terminate script processing when label is found + * + * @v rc		Line processing status + * @ret terminate	Terminate script processing + */ +static int terminate_on_label_found ( int rc ) { +	return ( rc == 0 ); +} + +/** + * "goto" command + * + * @v argc		Argument count + * @v argv		Argument list + * @ret rc		Return status code + */ +static int goto_exec ( int argc, char **argv ) { +	struct goto_options opts; +	size_t saved_offset; +	int rc; + +	/* Parse options */ +	if ( ( rc = parse_options ( argc, argv, &goto_cmd, &opts ) ) != 0 ) +		return rc; + +	/* Sanity check */ +	if ( ! current_image ) { +		rc = -ENOTTY; +		printf ( "Not in a script: %s\n", strerror ( rc ) ); +		return rc; +	} + +	/* Parse label */ +	goto_label = argv[optind]; + +	/* Find label */ +	saved_offset = script_offset; +	if ( ( rc = process_script ( current_image, goto_find_label, +				     terminate_on_label_found ) ) != 0 ) { +		script_offset = saved_offset; +		DBGC ( current_image, "[%04zx] No such label :%s\n", +		       script_offset, goto_label ); +		return rc; +	} + +	/* Terminate processing of current command */ +	shell_stop ( SHELL_STOP_COMMAND ); + +	return 0; +} + +/** "goto" command */ +struct command goto_command __command = { +	.name = "goto", +	.exec = goto_exec, +}; + +/** "prompt" options */ +struct prompt_options { +	/** Key to wait for */ +	unsigned int key; +	/** Timeout */ +	unsigned long timeout; +}; + +/** "prompt" option list */ +static struct option_descriptor prompt_opts[] = { +	OPTION_DESC ( "key", 'k', required_argument, +		      struct prompt_options, key, parse_key ), +	OPTION_DESC ( "timeout", 't', required_argument, +		      struct prompt_options, timeout, parse_timeout ), +}; + +/** "prompt" command descriptor */ +static struct command_descriptor prompt_cmd = +	COMMAND_DESC ( struct prompt_options, prompt_opts, 0, MAX_ARGUMENTS, +		       "[<text>]" ); + +/** + * "prompt" command + * + * @v argc		Argument count + * @v argv		Argument list + * @ret rc		Return status code + */ +static int prompt_exec ( int argc, char **argv ) { +	struct prompt_options opts; +	char *text; +	int rc; + +	/* Parse options */ +	if ( ( rc = parse_options ( argc, argv, &prompt_cmd, &opts ) ) != 0 ) +		goto err_parse; + +	/* Parse prompt text */ +	text = concat_args ( &argv[optind] ); +	if ( ! text ) { +		rc = -ENOMEM; +		goto err_concat; +	} + +	/* Display prompt and wait for key */ +	if ( ( rc = prompt ( text, opts.timeout, opts.key ) ) != 0 ) +		goto err_prompt; + +	/* Free prompt text */ +	free ( text ); + +	return 0; + + err_prompt: +	free ( text ); + err_concat: + err_parse: +	return rc; +} + +/** "prompt" command */ +struct command prompt_command __command = { +	.name = "prompt", +	.exec = prompt_exec, +}; diff --git a/roms/ipxe/src/image/segment.c b/roms/ipxe/src/image/segment.c new file mode 100644 index 00000000..86fe4266 --- /dev/null +++ b/roms/ipxe/src/image/segment.c @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** + * @file + * + * Executable image segments + * + */ + +#include <errno.h> +#include <ipxe/uaccess.h> +#include <ipxe/io.h> +#include <ipxe/errortab.h> +#include <ipxe/segment.h> + +/** + * Segment-specific error messages + * + * This error happens sufficiently often to merit a user-friendly + * description. + */ +#define ERANGE_SEGMENT __einfo_error ( EINFO_ERANGE_SEGMENT ) +#define EINFO_ERANGE_SEGMENT \ +	__einfo_uniqify ( EINFO_ERANGE, 0x01, "Requested memory not available" ) +struct errortab segment_errors[] __errortab = { +	__einfo_errortab ( EINFO_ERANGE_SEGMENT ), +}; + +/** + * Prepare segment for loading + * + * @v segment		Segment start + * @v filesz		Size of the "allocated bytes" portion of the segment + * @v memsz		Size of the segment + * @ret rc		Return status code + */ +int prep_segment ( userptr_t segment, size_t filesz, size_t memsz ) { +	struct memory_map memmap; +	physaddr_t start = user_to_phys ( segment, 0 ); +	physaddr_t mid = user_to_phys ( segment, filesz ); +	physaddr_t end = user_to_phys ( segment, memsz ); +	unsigned int i; + +	DBG ( "Preparing segment [%lx,%lx,%lx)\n", start, mid, end ); + +	/* Sanity check */ +	if ( filesz > memsz ) { +		DBG ( "Insane segment [%lx,%lx,%lx)\n", start, mid, end ); +		return -EINVAL; +	} + +	/* Get a fresh memory map.  This allows us to automatically +	 * avoid treading on any regions that Etherboot is currently +	 * editing out of the memory map. +	 */ +	get_memmap ( &memmap ); + +	/* Look for a suitable memory region */ +	for ( i = 0 ; i < memmap.count ; i++ ) { +		if ( ( start >= memmap.regions[i].start ) && +		     ( end <= memmap.regions[i].end ) ) { +			/* Found valid region: zero bss and return */ +			memset_user ( segment, filesz, 0, ( memsz - filesz ) ); +			return 0; +		} +	} + +	/* No suitable memory region found */ +	DBG ( "Segment [%lx,%lx,%lx) does not fit into available memory\n", +	      start, mid, end ); +	return -ERANGE_SEGMENT; +} | 
