From e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb Mon Sep 17 00:00:00 2001 From: Yang Zhang Date: Fri, 28 Aug 2015 09:58:54 +0800 Subject: Add qemu 2.4.0 Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5 Signed-off-by: Yang Zhang --- qemu/roms/ipxe/src/image/efi_image.c | 316 +++++++++++ qemu/roms/ipxe/src/image/elf.c | 185 +++++++ qemu/roms/ipxe/src/image/embedded.c | 91 +++ qemu/roms/ipxe/src/image/png.c | 1007 ++++++++++++++++++++++++++++++++++ qemu/roms/ipxe/src/image/pnm.c | 415 ++++++++++++++ qemu/roms/ipxe/src/image/script.c | 423 ++++++++++++++ qemu/roms/ipxe/src/image/segment.c | 91 +++ 7 files changed, 2528 insertions(+) create mode 100644 qemu/roms/ipxe/src/image/efi_image.c create mode 100644 qemu/roms/ipxe/src/image/elf.c create mode 100644 qemu/roms/ipxe/src/image/embedded.c create mode 100644 qemu/roms/ipxe/src/image/png.c create mode 100644 qemu/roms/ipxe/src/image/pnm.c create mode 100644 qemu/roms/ipxe/src/image/script.c create mode 100644 qemu/roms/ipxe/src/image/segment.c (limited to 'qemu/roms/ipxe/src/image') diff --git a/qemu/roms/ipxe/src/image/efi_image.c b/qemu/roms/ipxe/src/image/efi_image.c new file mode 100644 index 000000000..b7d8f9c6e --- /dev/null +++ b/qemu/roms/ipxe/src/image/efi_image.c @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2008 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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/qemu/roms/ipxe/src/image/elf.c b/qemu/roms/ipxe/src/image/elf.c new file mode 100644 index 000000000..51636a8e9 --- /dev/null +++ b/qemu/roms/ipxe/src/image/elf.c @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2007 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include + +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/qemu/roms/ipxe/src/image/embedded.c b/qemu/roms/ipxe/src/image/embedded.c new file mode 100644 index 000000000..6358378fb --- /dev/null +++ b/qemu/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 +#include +#include +#include + +/* 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/qemu/roms/ipxe/src/image/png.c b/qemu/roms/ipxe/src/image/png.c new file mode 100644 index 000000000..c14608553 --- /dev/null +++ b/qemu/roms/ipxe/src/image/png.c @@ -0,0 +1,1007 @@ +/* + * Copyright (C) 2014 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/** @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/qemu/roms/ipxe/src/image/pnm.c b/qemu/roms/ipxe/src/image/pnm.c new file mode 100644 index 000000000..af9e571a2 --- /dev/null +++ b/qemu/roms/ipxe/src/image/pnm.c @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2013 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include + +/** + * 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/qemu/roms/ipxe/src/image/script.c b/qemu/roms/ipxe/src/image/script.c new file mode 100644 index 000000000..5328da8b4 --- /dev/null +++ b/qemu/roms/ipxe/src/image/script.c @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2007 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** 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, "