Commit 93996cf7 authored by longpanda's avatar longpanda
Browse files

1. Optimization for WIMBOOT mode.

2. Add WIMBOOT for UEFI mode.
parent ca62128f
/*
* 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 (at your option) 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
*
* EFI block device
*
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "wimboot.h"
#include "vdisk.h"
#include "efi.h"
#include "efipath.h"
#include "efiblock.h"
/** A block I/O device */
struct efi_block {
/** EFI block I/O protocol */
EFI_BLOCK_IO_PROTOCOL block;
/** Device path */
EFI_DEVICE_PATH_PROTOCOL *path;
/** Starting LBA */
uint64_t lba;
/** Name */
const char *name;
};
/**
* Reset block I/O protocol
*
* @v this Block I/O protocol
* @v extended Perform extended verification
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI
efi_reset_blocks ( EFI_BLOCK_IO_PROTOCOL *this, BOOLEAN extended ) {
struct efi_block *block =
container_of ( this, struct efi_block, block );
void *retaddr = __builtin_return_address ( 0 );
DBG2 ( "EFI %s %sreset -> %p\n",
block->name, ( extended ? "extended " : "" ), retaddr );
return 0;
}
/**
* Read blocks
*
* @v this Block I/O protocol
* @v media Media ID
* @v lba Starting LBA
* @v len Length of data
* @v data Data buffer
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI
efi_read_blocks ( EFI_BLOCK_IO_PROTOCOL *this, UINT32 media, EFI_LBA lba,
UINTN len, VOID *data ) {
struct efi_block *block =
container_of ( this, struct efi_block, block );
void *retaddr = __builtin_return_address ( 0 );
DBG2 ( "EFI %s read media %08x LBA %#llx to %p+%zx -> %p\n",
block->name, media, lba, data, ( ( size_t ) len ), retaddr );
vdisk_read ( ( lba + block->lba ), ( len / VDISK_SECTOR_SIZE ), data );
return 0;
}
/**
* Write blocks
*
* @v this Block I/O protocol
* @v media Media ID
* @v lba Starting LBA
* @v len Length of data
* @v data Data buffer
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI
efi_write_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused,
UINT32 media __unused, EFI_LBA lba __unused,
UINTN len __unused, VOID *data __unused ) {
struct efi_block *block =
container_of ( this, struct efi_block, block );
void *retaddr = __builtin_return_address ( 0 );
DBG2 ( "EFI %s write media %08x LBA %#llx from %p+%zx -> %p\n",
block->name, media, lba, data, ( ( size_t ) len ), retaddr );
return EFI_WRITE_PROTECTED;
}
/**
* Flush block operations
*
* @v this Block I/O protocol
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI
efi_flush_blocks ( EFI_BLOCK_IO_PROTOCOL *this ) {
struct efi_block *block =
container_of ( this, struct efi_block, block );
void *retaddr = __builtin_return_address ( 0 );
DBG2 ( "EFI %s flush -> %p\n", block->name, retaddr );
return 0;
}
/** GUID used in vendor device path */
#define EFIBLOCK_GUID { \
0x1322d197, 0x15dc, 0x4a45, \
{ 0xa6, 0xa4, 0xfa, 0x57, 0x05, 0x4e, 0xa6, 0x14 } \
}
/**
* Initialise vendor device path
*
* @v name Variable name
*/
#define EFIBLOCK_DEVPATH_VENDOR_INIT( name ) { \
.Header = EFI_DEVPATH_INIT ( name, HARDWARE_DEVICE_PATH, \
HW_VENDOR_DP ), \
.Guid = EFIBLOCK_GUID, \
}
/**
* Initialise ATA device path
*
* @v name Variable name
*/
#define EFIBLOCK_DEVPATH_ATA_INIT( name ) { \
.Header = EFI_DEVPATH_INIT ( name, MESSAGING_DEVICE_PATH, \
MSG_ATAPI_DP ), \
.PrimarySecondary = 0, \
.SlaveMaster = 0, \
.Lun = 0, \
}
/**
* Initialise hard disk device path
*
* @v name Variable name
*/
#define EFIBLOCK_DEVPATH_HD_INIT( name ) { \
.Header = EFI_DEVPATH_INIT ( name, MEDIA_DEVICE_PATH, \
MEDIA_HARDDRIVE_DP ), \
.PartitionNumber = 1, \
.PartitionStart = VDISK_PARTITION_LBA, \
.PartitionSize = VDISK_PARTITION_COUNT, \
.Signature[0] = ( ( VDISK_MBR_SIGNATURE >> 0 ) & 0xff ), \
.Signature[1] = ( ( VDISK_MBR_SIGNATURE >> 8 ) & 0xff ), \
.Signature[2] = ( ( VDISK_MBR_SIGNATURE >> 16 ) & 0xff ), \
.Signature[3] = ( ( VDISK_MBR_SIGNATURE >> 24 ) & 0xff ), \
.MBRType = MBR_TYPE_PCAT, \
.SignatureType = SIGNATURE_TYPE_MBR, \
}
/** Virtual disk media */
static EFI_BLOCK_IO_MEDIA efi_vdisk_media = {
.MediaId = VDISK_MBR_SIGNATURE,
.MediaPresent = TRUE,
.LogicalPartition = FALSE,
.ReadOnly = TRUE,
.BlockSize = VDISK_SECTOR_SIZE,
.LastBlock = ( VDISK_COUNT - 1 ),
};
/** Virtual disk device path */
static struct {
VENDOR_DEVICE_PATH vendor;
ATAPI_DEVICE_PATH ata;
EFI_DEVICE_PATH_PROTOCOL end;
} __attribute__ (( packed )) efi_vdisk_path = {
.vendor = EFIBLOCK_DEVPATH_VENDOR_INIT ( efi_vdisk_path.vendor ),
.ata = EFIBLOCK_DEVPATH_ATA_INIT ( efi_vdisk_path.ata ),
.end = EFI_DEVPATH_END_INIT ( efi_vdisk_path.end ),
};
/** Virtual disk device */
static struct efi_block efi_vdisk = {
.block = {
.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION,
.Media = &efi_vdisk_media,
.Reset = efi_reset_blocks,
.ReadBlocks = efi_read_blocks,
.WriteBlocks = efi_write_blocks,
.FlushBlocks = efi_flush_blocks,
},
.path = &efi_vdisk_path.vendor.Header,
.lba = 0,
.name = "vdisk",
};
/** Virtual partition media */
static EFI_BLOCK_IO_MEDIA efi_vpartition_media = {
.MediaId = VDISK_MBR_SIGNATURE,
.MediaPresent = TRUE,
.LogicalPartition = TRUE,
.ReadOnly = TRUE,
.BlockSize = VDISK_SECTOR_SIZE,
.LastBlock = ( VDISK_PARTITION_COUNT - 1 ),
};
/** Virtual partition device path */
static struct {
VENDOR_DEVICE_PATH vendor;
ATAPI_DEVICE_PATH ata;
HARDDRIVE_DEVICE_PATH hd;
EFI_DEVICE_PATH_PROTOCOL end;
} __attribute__ (( packed )) efi_vpartition_path = {
.vendor = EFIBLOCK_DEVPATH_VENDOR_INIT ( efi_vpartition_path.vendor ),
.ata = EFIBLOCK_DEVPATH_ATA_INIT ( efi_vpartition_path.ata ),
.hd = EFIBLOCK_DEVPATH_HD_INIT ( efi_vpartition_path.hd ),
.end = EFI_DEVPATH_END_INIT ( efi_vpartition_path.end ),
};
/** Virtual partition device */
static struct efi_block efi_vpartition = {
.block = {
.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION,
.Media = &efi_vpartition_media,
.Reset = efi_reset_blocks,
.ReadBlocks = efi_read_blocks,
.WriteBlocks = efi_write_blocks,
.FlushBlocks = efi_flush_blocks,
},
.path = &efi_vpartition_path.vendor.Header,
.lba = VDISK_PARTITION_LBA,
.name = "vpartition",
};
/**
* Install block I/O protocols
*
* @ret vdisk New virtual disk handle
* @ret vpartition New virtual partition handle
*/
void efi_install ( EFI_HANDLE *vdisk, EFI_HANDLE *vpartition ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
EFI_STATUS efirc;
/* Install virtual disk */
if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
vdisk,
&efi_block_io_protocol_guid, &efi_vdisk.block,
&efi_device_path_protocol_guid, efi_vdisk.path,
NULL ) ) != 0 ) {
die ( "Could not install disk block I/O protocols: %#lx\n",
( ( unsigned long ) efirc ) );
}
/* Install virtual partition */
if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
vpartition,
&efi_block_io_protocol_guid, &efi_vpartition.block,
&efi_device_path_protocol_guid, efi_vpartition.path,
NULL ) ) != 0 ) {
die ( "Could not install partition block I/O protocols: %#lx\n",
( ( unsigned long ) efirc ) );
}
}
/** Boot image path */
static struct {
VENDOR_DEVICE_PATH vendor;
ATAPI_DEVICE_PATH ata;
HARDDRIVE_DEVICE_PATH hd;
struct {
EFI_DEVICE_PATH header;
CHAR16 name[ sizeof ( EFI_REMOVABLE_MEDIA_FILE_NAME ) /
sizeof ( CHAR16 ) ];
} __attribute__ (( packed )) file;
EFI_DEVICE_PATH_PROTOCOL end;
} __attribute__ (( packed )) efi_bootmgfw_path = {
.vendor = EFIBLOCK_DEVPATH_VENDOR_INIT ( efi_bootmgfw_path.vendor ),
.ata = EFIBLOCK_DEVPATH_ATA_INIT ( efi_bootmgfw_path.ata ),
.hd = EFIBLOCK_DEVPATH_HD_INIT ( efi_bootmgfw_path.hd ),
.file = {
.header = EFI_DEVPATH_INIT ( efi_bootmgfw_path.file,
MEDIA_DEVICE_PATH,
MEDIA_FILEPATH_DP ),
.name = EFI_REMOVABLE_MEDIA_FILE_NAME,
},
.end = EFI_DEVPATH_END_INIT ( efi_bootmgfw_path.end ),
};
/** Boot image path */
EFI_DEVICE_PATH_PROTOCOL *bootmgfw_path = &efi_bootmgfw_path.vendor.Header;
#ifndef _EFIBLOCK_H
#define _EFIBLOCK_H
/*
* 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 (at your option) 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
*
* EFI block device
*
*/
#include "efi.h"
#include "efi/Protocol/BlockIo.h"
#include "efi/Protocol/DevicePath.h"
extern void efi_install ( EFI_HANDLE *vdisk, EFI_HANDLE *vpartition );
extern EFI_DEVICE_PATH_PROTOCOL *bootmgfw_path;
#endif /* _EFIBLOCK_H */
/*
* 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 (at your option) 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
*
* EFI boot manager invocation
*
*/
#include <stdio.h>
#include <string.h>
#include "wimboot.h"
#include "cmdline.h"
#include "vdisk.h"
#include "pause.h"
#include "efi.h"
#include "efi/Protocol/GraphicsOutput.h"
#include "efipath.h"
#include "efiboot.h"
/** Original OpenProtocol() method */
static EFI_OPEN_PROTOCOL orig_open_protocol;
/**
* Intercept OpenProtocol()
*
* @v handle EFI handle
* @v protocol Protocol GUID
* @v interface Opened interface
* @v agent_handle Agent handle
* @v controller_handle Controller handle
* @v attributes Attributes
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI
efi_open_protocol_wrapper ( EFI_HANDLE handle, EFI_GUID *protocol,
VOID **interface, EFI_HANDLE agent_handle,
EFI_HANDLE controller_handle, UINT32 attributes ) {
static unsigned int count;
EFI_STATUS efirc;
/* Open the protocol */
if ( ( efirc = orig_open_protocol ( handle, protocol, interface,
agent_handle, controller_handle,
attributes ) ) != 0 ) {
return efirc;
}
/* Block first attempt by bootmgfw.efi to open
* EFI_GRAPHICS_OUTPUT_PROTOCOL. This forces error messages
* to be displayed in text mode (thereby avoiding the totally
* blank error screen if the fonts are missing). We must
* allow subsequent attempts to succeed, otherwise the OS will
* fail to boot.
*/
if ( ( memcmp ( protocol, &efi_graphics_output_protocol_guid,
sizeof ( *protocol ) ) == 0 ) &&
( count++ == 0 ) && ( ! cmdline_gui ) ) {
DBG ( "Forcing text mode output\n" );
return EFI_INVALID_PARAMETER;
}
return 0;
}
/**
* Boot from EFI device
*
* @v file Virtual file
* @v path Device path
* @v device Device handle
*/
void efi_boot ( struct vdisk_file *file, EFI_DEVICE_PATH_PROTOCOL *path,
EFI_HANDLE device ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
union {
EFI_LOADED_IMAGE_PROTOCOL *image;
void *intf;
} loaded;
EFI_PHYSICAL_ADDRESS phys;
void *data;
unsigned int pages;
EFI_HANDLE handle;
EFI_STATUS efirc;
/* Allocate memory */
pages = ( ( file->len + PAGE_SIZE - 1 ) / PAGE_SIZE );
if ( ( efirc = bs->AllocatePages ( AllocateAnyPages,
EfiBootServicesData, pages,
&phys ) ) != 0 ) {
die ( "Could not allocate %d pages: %#lx\n",
pages, ( ( unsigned long ) efirc ) );
}
data = ( ( void * ) ( intptr_t ) phys );
/* Read image */
file->read ( file, data, 0, file->len );
DBG ( "Read %s\n", file->name );
/* Load image */
if ( ( efirc = bs->LoadImage ( FALSE, efi_image_handle, path, data,
file->len, &handle ) ) != 0 ) {
die ( "Could not load %s: %#lx\n",
file->name, ( ( unsigned long ) efirc ) );
}
DBG ( "Loaded %s\n", file->name );
/* Get loaded image protocol */
if ( ( efirc = bs->OpenProtocol ( handle,
&efi_loaded_image_protocol_guid,
&loaded.intf, efi_image_handle, NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
die ( "Could not get loaded image protocol for %s: %#lx\n",
file->name, ( ( unsigned long ) efirc ) );
}
/* Force correct device handle */
if ( loaded.image->DeviceHandle != device ) {
DBG ( "Forcing correct DeviceHandle (%p->%p)\n",
loaded.image->DeviceHandle, device );
loaded.image->DeviceHandle = device;
}
/* Intercept calls to OpenProtocol() */
orig_open_protocol =
loaded.image->SystemTable->BootServices->OpenProtocol;
loaded.image->SystemTable->BootServices->OpenProtocol =
efi_open_protocol_wrapper;
/* Start image */
if ( cmdline_pause )
pause();
if ( ( efirc = bs->StartImage ( handle, NULL, NULL ) ) != 0 ) {
die ( "Could not start %s: %#lx\n",
file->name, ( ( unsigned long ) efirc ) );
}
die ( "%s returned\n", file->name );
}
#ifndef _EFIBOOT_H
#define _EFIBOOT_H
/*
* 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 (at your option) 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
*
* EFI boot manager invocation
*
*/
#include "efi.h"
#include "efi/Protocol/DevicePath.h"
struct vdisk_file;
extern void efi_boot ( struct vdisk_file *file, EFI_DEVICE_PATH_PROTOCOL *path,
EFI_HANDLE device );
#endif /* _EFIBOOT_H */
/*
* 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 (at your option) 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
*
* EFI file system access
*
*/
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <wchar.h>
#include "wimboot.h"
#include "vdisk.h"
#include "cmdline.h"
#include "wimpatch.h"
#include "wimfile.h"
#include "efi.h"
#include "efifile.h"
/** bootmgfw.efi path within WIM */
static const wchar_t bootmgfw_path[] = L"\\Windows\\Boot\\EFI\\bootmgfw.efi";
/** Other paths within WIM */
static const wchar_t *efi_wim_paths[] = {
L"\\Windows\\Boot\\DVD\\EFI\\boot.sdi",
L"\\Windows\\Boot\\DVD\\EFI\\BCD",
L"\\Windows\\Boot\\Fonts\\segmono_boot.ttf",
L"\\Windows\\Boot\\Fonts\\segoen_slboot.ttf",
L"\\Windows\\Boot\\Fonts\\segoe_slboot.ttf",
L"\\Windows\\Boot\\Fonts\\wgl4_boot.ttf",
L"\\sms\\boot\\boot.sdi",
NULL
};
/** bootmgfw.efi file */
struct vdisk_file *bootmgfw;
/**
* Get architecture-specific boot filename
*
* @ret bootarch Architecture-specific boot filename
*/
static const CHAR16 * efi_bootarch ( void ) {
static const CHAR16 bootarch_full[] = EFI_REMOVABLE_MEDIA_FILE_NAME;
const CHAR16 *tmp;
const CHAR16 *bootarch = bootarch_full;
for ( tmp = bootarch_full ; *tmp ; tmp++ ) {
if ( *tmp == L'\\' )
bootarch = ( tmp + 1 );
}
return bootarch;
}
/**
* Read from EFI file
*
* @v vfile Virtual file
* @v data Data buffer
* @v offset Offset
* @v len Length
*/
static void efi_read_file ( struct vdisk_file *vfile, void *data,
size_t offset, size_t len ) {
#if 0
EFI_FILE_PROTOCOL *file = vfile->opaque;
UINTN size = len;
EFI_STATUS efirc;
/* Set file position */
if ( ( efirc = file->SetPosition ( file, offset ) ) != 0 ) {
die ( "Could not set file position: %#lx\n",
( ( unsigned long ) efirc ) );
}
/* Read from file */
if ( ( efirc = file->Read ( file, &size, data ) ) != 0 ) {
die ( "Could not read from file: %#lx\n",
( ( unsigned long ) efirc ) );
}
#endif /* #if 0 */
(void)vfile;
pfventoy_file_read((const char *)vfile->opaque, (int)offset, (int)len, data);
}
/**
* Patch BCD file
*
* @v vfile Virtual file
* @v data Data buffer
* @v offset Offset
* @v len Length
*/
static void efi_patch_bcd ( struct vdisk_file *vfile __unused, void *data,
size_t offset, size_t len ) {
static const wchar_t search[] = L".exe";
static const wchar_t replace[] = L".efi";
size_t i;
/* Do nothing if BCD patching is disabled */
if ( cmdline_rawbcd )
return;
/* Patch any occurrences of ".exe" to ".efi". In the common
* simple cases, this allows the same BCD file to be used for
* both BIOS and UEFI systems.
*/
for ( i = 0 ; ( i + sizeof ( search ) ) < len ; i++ ) {
if ( wcscasecmp ( ( data + i ), search ) == 0 ) {
memcpy ( ( data + i ), replace, sizeof ( replace ) );
DBG ( "...patched BCD at %#zx: \"%ls\" to \"%ls\"\n",
( offset + i ), search, replace );
}
}
}
/**
* Extract files from EFI file system
*
* @v handle Device handle
*/
void efi_extract ( EFI_HANDLE handle ) {
struct vdisk_file *wim = NULL;
struct vdisk_file *vfile;
CHAR16 wname[64];
int i, j, k;
char *pos;
size_t len = 0;
(void)handle;
/* Read root directory */
for (i = 0; i < cmdline_vf_num; i++) {
pos = strchr(cmdline_vf_path[i], ':');
*pos = 0;
k = (int)strlen(cmdline_vf_path[i]);
memset(wname, 0, sizeof(wname));
for (j = 0; j < k; j++)
{
wname[j] = cmdline_vf_path[i][j];
}
len = pfventoy_file_size(pos + 1);
vfile = vdisk_add_file (cmdline_vf_path[i], pos + 1, len, efi_read_file);
/* Check for special-case files */
if ( ( wcscasecmp ( wname, efi_bootarch() ) == 0 ) ||
( wcscasecmp ( wname, L"bootmgfw.efi" ) == 0 ) ) {
DBG ( "...found bootmgfw.efi file %ls\n", wname );
bootmgfw = vfile;
} else if ( wcscasecmp ( wname, L"BCD" ) == 0 ) {
DBG ( "...found BCD\n" );
vdisk_patch_file ( vfile, efi_patch_bcd );
} else if ( wcscasecmp ( ( wname + ( wcslen ( wname ) - 4 ) ),
L".wim" ) == 0 ) {
DBG ( "...found WIM file %ls\n", wname );
wim = vfile;
}
}
/* Process WIM image */
if ( wim ) {
vdisk_patch_file ( wim, patch_wim );
if ( ( ! bootmgfw ) &&
( bootmgfw = wim_add_file ( wim, cmdline_index,
bootmgfw_path,
efi_bootarch() ) ) ) {
DBG ( "...extracted %ls\n", bootmgfw_path );
}
wim_add_files ( wim, cmdline_index, efi_wim_paths );
}
/* Check that we have a boot file */
if ( ! bootmgfw ) {
die ( "FATAL: no %ls or bootmgfw.efi found\n",
efi_bootarch() );
}
}
#ifndef _EFIFILE_H
#define _EFIFILE_H
/*
* 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 (at your option) 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
*
* EFI file system access
*
*/
#include "efi.h"
#include "efi/Protocol/SimpleFileSystem.h"
#include "efi/Guid/FileInfo.h"
struct vdisk_file;
extern struct vdisk_file *bootmgfw;
extern void efi_extract ( EFI_HANDLE handle );
#endif /* _EFIFILE_H */
/*
* 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 (at your option) 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
*
* EFI GUIDs
*
*/
#include "wimboot.h"
#include "efi.h"
#include "efi/Protocol/BlockIo.h"
#include "efi/Protocol/DevicePath.h"
#include "efi/Protocol/GraphicsOutput.h"
#include "efi/Protocol/LoadedImage.h"
#include "efi/Protocol/SimpleFileSystem.h"
/** Block I/O protocol GUID */
EFI_GUID efi_block_io_protocol_guid
= EFI_BLOCK_IO_PROTOCOL_GUID;
/** Device path protocol GUID */
EFI_GUID efi_device_path_protocol_guid
= EFI_DEVICE_PATH_PROTOCOL_GUID;
/** Graphics output protocol GUID */
EFI_GUID efi_graphics_output_protocol_guid
= EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
/** Loaded image protocol GUID */
EFI_GUID efi_loaded_image_protocol_guid
= EFI_LOADED_IMAGE_PROTOCOL_GUID;
/** Simple file system protocol GUID */
EFI_GUID efi_simple_file_system_protocol_guid
= EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
/*
* 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 (at your option) 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
*
* EFI entry point
*
*/
#include <stdio.h>
#include "wimboot.h"
#include "cmdline.h"
#include "efi.h"
#include "efifile.h"
#include "efiblock.h"
#include "efiboot.h"
/**
* Process command line
*
* @v loaded Loaded image protocol
*/
static void efi_cmdline ( EFI_LOADED_IMAGE_PROTOCOL *loaded ) {
size_t cmdline_len = ( loaded->LoadOptionsSize / sizeof ( wchar_t ) );
char cmdline[ cmdline_len + 1 /* NUL */ ];
const wchar_t *wcmdline = loaded->LoadOptions;
/* Convert command line to ASCII */
snprintf ( cmdline, sizeof ( cmdline ), "%ls", wcmdline );
/* Process command line */
process_cmdline ( cmdline );
}
/**
* EFI entry point
*
* @v image_handle Image handle
* @v systab EFI system table
* @ret efirc EFI status code
*/
EFI_STATUS EFIAPI efi_main ( EFI_HANDLE image_handle,
EFI_SYSTEM_TABLE *systab ) {
EFI_BOOT_SERVICES *bs;
union {
EFI_LOADED_IMAGE_PROTOCOL *image;
void *interface;
} loaded;
EFI_HANDLE vdisk = NULL;
EFI_HANDLE vpartition = NULL;
EFI_STATUS efirc;
/* Record EFI handle and system table */
efi_image_handle = image_handle;
efi_systab = systab;
bs = systab->BootServices;
/* Initialise stack cookie */
init_cookie();
/* Print welcome banner */
printf ( "\n\nBooting wim file...... (This may take a few minutes, please wait)\n\n");
// printf ( "\n\nwimboot " VERSION " -- Windows Imaging Format "
// "bootloader -- https://ipxe.org/wimboot\n\n" );
/* Get loaded image protocol */
if ( ( efirc = bs->OpenProtocol ( image_handle,
&efi_loaded_image_protocol_guid,
&loaded.interface, image_handle, NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
die ( "Could not open loaded image protocol: %#lx\n",
( ( unsigned long ) efirc ) );
}
/* Process command line */
efi_cmdline ( loaded.image );
/* Extract files from file system */
efi_extract ( loaded.image->DeviceHandle );
/* Install virtual disk */
efi_install ( &vdisk, &vpartition );
/* Invoke boot manager */
efi_boot ( bootmgfw, bootmgfw_path, vpartition );
return 0;
}
/*
* 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 (at your option) 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
*
* EFI device paths
*
*/
#include "wimboot.h"
#include "efi.h"
#include "efipath.h"
/**
* Find end of device path
*
* @v path Path to device
* @ret path_end End of device path
*/
EFI_DEVICE_PATH_PROTOCOL * efi_devpath_end ( EFI_DEVICE_PATH_PROTOCOL *path ) {
while ( path->Type != END_DEVICE_PATH_TYPE ) {
path = ( ( ( void * ) path ) +
/* There's this amazing new-fangled thing known as
* a UINT16, but who wants to use one of those? */
( ( path->Length[1] << 8 ) | path->Length[0] ) );
}
return path;
}
#ifndef _EFIPATH_H
#define _EFIPATH_H
/*
* 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 (at your option) 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
*
* EFI device paths
*
*/
#include "efi.h"
#include "efi/Protocol/DevicePath.h"
/**
* Initialise device path
*
* @v name Variable name
* @v type Type
* @v subtype Subtype
*/
#define EFI_DEVPATH_INIT( name, type, subtype ) { \
.Type = (type), \
.SubType = (subtype), \
.Length[0] = ( sizeof (name) & 0xff ), \
.Length[1] = ( sizeof (name) >> 8 ), \
}
/**
* Initialise device path
*
* @v path Device path
* @v type Type
* @v subtype Subtype
* @v len Length
*/
static inline __attribute__ (( always_inline )) void
efi_devpath_init ( EFI_DEVICE_PATH_PROTOCOL *path, unsigned int type,
unsigned int subtype, size_t len ) {
path->Type = type;
path->SubType = subtype;
path->Length[0] = ( len & 0xff );
path->Length[1] = ( len >> 8 );
}
/**
* Initialise device path end
*
* @v name Variable name
*/
#define EFI_DEVPATH_END_INIT( name ) \
EFI_DEVPATH_INIT ( name, END_DEVICE_PATH_TYPE, \
END_ENTIRE_DEVICE_PATH_SUBTYPE )
/**
* Initialise device path end
*
* @v path Device path
*/
static inline __attribute__ (( always_inline )) void
efi_devpath_end_init ( EFI_DEVICE_PATH_PROTOCOL *path ) {
efi_devpath_init ( path, END_DEVICE_PATH_TYPE,
END_ENTIRE_DEVICE_PATH_SUBTYPE, sizeof ( *path ) );
}
extern EFI_DEVICE_PATH_PROTOCOL *
efi_devpath_end ( EFI_DEVICE_PATH_PROTOCOL *path );
#endif /* _EFIPATH_H */
/*
* 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 (at your option) 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
*
* EFI relocations
*
* Derived from iPXE's elf2efi.c
*
*/
#define PACKAGE "wimboot"
#define PACKAGE_VERSION VERSION
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <getopt.h>
#include <bfd.h>
#include "efi.h"
#include "efi/IndustryStandard/PeImage.h"
#include "wimboot.h"
#define eprintf(...) fprintf ( stderr, __VA_ARGS__ )
/* Maintain compatibility with binutils 2.34 */
#ifndef bfd_get_section_vma
#define bfd_get_section_vma(bfd, ptr) bfd_section_vma(ptr)
#endif
#ifndef bfd_get_section_flags
#define bfd_get_section_flags(bfd, ptr) bfd_section_flags(ptr)
#endif
/** PE header maximum length
*
* This maximum length is guaranteed by the fact that the PE headers
* have to fit entirely before the start of the bzImage header.
*/
#define PE_HEADER_LEN 512
/** .reloc section index */
#define RELOC_SECTION_INDEX 3
/** PE relocations */
struct pe_relocs {
struct pe_relocs *next;
unsigned long start_rva;
unsigned int used_relocs;
unsigned int total_relocs;
uint16_t *relocs;
};
/** Command-line options */
struct options {
/** Verbosity */
int verbosity;
};
/**
* Allocate memory
*
* @v len Length of memory to allocate
* @ret ptr Pointer to allocated memory
*/
static void * xmalloc ( size_t len ) {
void *ptr;
ptr = malloc ( len );
if ( ! ptr ) {
eprintf ( "Could not allocate %zd bytes\n", len );
exit ( 1 );
}
return ptr;
}
/**
* Write to file
*
* @v fd File descriptor
* @v data Data
* @v len Length of data
*/
static void xwrite ( int fd, const void *data, size_t len ) {
ssize_t written;
written = write ( fd, data, len );
if ( written < 0 ) {
eprintf ( "Could not write %zd bytes: %s\n",
len, strerror ( errno ) );
exit ( 1 );
}
if ( ( size_t ) written != len ) {
eprintf ( "Wrote only %zd of %zd bytes\n", written, len );
exit ( 1 );
}
}
/**
* Seek to file position
*
* @v fd File descriptor
* @v offset Offset
* @v whence Origin
*/
static void xlseek ( int fd, off_t offset, int whence ) {
off_t pos;
pos = lseek ( fd, offset, whence );
if ( pos < 0 ) {
eprintf ( "Could not seek: %s\n", strerror ( errno ) );
exit ( 1 );
}
}
/**
* Close file
*
* @v fd File descriptor
*/
static void xclose ( int fd ) {
if ( close ( fd ) < 0 ) {
eprintf ( "Could not close: %s\n", strerror ( errno ) );
exit ( 1 );
}
}
/**
* Open input BFD file
*
* @v filename File name
* @ret ibfd BFD file
*/
static bfd * open_input_bfd ( const char *filename ) {
bfd *bfd;
/* Open the file */
bfd = bfd_openr ( filename, NULL );
if ( ! bfd ) {
eprintf ( "Cannot open %s: ", filename );
bfd_perror ( NULL );
exit ( 1 );
}
/* The call to bfd_check_format() must be present, otherwise
* we get a segfault from later BFD calls.
*/
if ( ! bfd_check_format ( bfd, bfd_object ) ) {
eprintf ( "%s is not an object file: ", filename );
bfd_perror ( NULL );
exit ( 1 );
}
return bfd;
}
/**
* Read symbol table
*
* @v bfd BFD file
*/
static asymbol ** read_symtab ( bfd *bfd ) {
long symtab_size;
asymbol **symtab;
long symcount;
/* Get symbol table size */
symtab_size = bfd_get_symtab_upper_bound ( bfd );
if ( symtab_size < 0 ) {
bfd_perror ( "Could not get symbol table upper bound" );
exit ( 1 );
}
/* Allocate and read symbol table */
symtab = xmalloc ( symtab_size );
symcount = bfd_canonicalize_symtab ( bfd, symtab );
if ( symcount < 0 ) {
bfd_perror ( "Cannot read symbol table" );
exit ( 1 );
}
return symtab;
}
/**
* Read relocation table
*
* @v bfd BFD file
* @v symtab Symbol table
* @v section Section
* @v symtab Symbol table
* @ret reltab Relocation table
*/
static arelent ** read_reltab ( bfd *bfd, asymbol **symtab,
asection *section ) {
long reltab_size;
arelent **reltab;
long numrels;
/* Get relocation table size */
reltab_size = bfd_get_reloc_upper_bound ( bfd, section );
if ( reltab_size < 0 ) {
bfd_perror ( "Could not get relocation table upper bound" );
exit ( 1 );
}
/* Allocate and read relocation table */
reltab = xmalloc ( reltab_size );
numrels = bfd_canonicalize_reloc ( bfd, section, reltab, symtab );
if ( numrels < 0 ) {
bfd_perror ( "Cannot read relocation table" );
exit ( 1 );
}
return reltab;
}
/**
* Generate entry in PE relocation table
*
* @v pe_reltab PE relocation table
* @v rva RVA
* @v size Size of relocation entry
*/
static void generate_pe_reloc ( struct pe_relocs **pe_reltab,
unsigned long rva, size_t size ) {
unsigned long start_rva;
uint16_t reloc;
struct pe_relocs *pe_rel;
uint16_t *relocs;
/* Construct */
start_rva = ( rva & ~0xfff );
reloc = ( rva & 0xfff );
switch ( size ) {
case 8:
reloc |= 0xa000;
break;
case 4:
reloc |= 0x3000;
break;
case 2:
reloc |= 0x2000;
break;
default:
eprintf ( "Unsupported relocation size %zd\n", size );
exit ( 1 );
}
/* Locate or create PE relocation table */
for ( pe_rel = *pe_reltab ; pe_rel ; pe_rel = pe_rel->next ) {
if ( pe_rel->start_rva == start_rva )
break;
}
if ( ! pe_rel ) {
pe_rel = xmalloc ( sizeof ( *pe_rel ) );
memset ( pe_rel, 0, sizeof ( *pe_rel ) );
pe_rel->next = *pe_reltab;
*pe_reltab = pe_rel;
pe_rel->start_rva = start_rva;
}
/* Expand relocation list if necessary */
if ( pe_rel->used_relocs < pe_rel->total_relocs ) {
relocs = pe_rel->relocs;
} else {
pe_rel->total_relocs = ( pe_rel->total_relocs ?
( pe_rel->total_relocs * 2 ) : 256 );
relocs = xmalloc ( pe_rel->total_relocs *
sizeof ( pe_rel->relocs[0] ) );
memset ( relocs, 0,
pe_rel->total_relocs * sizeof ( pe_rel->relocs[0] ) );
memcpy ( relocs, pe_rel->relocs,
pe_rel->used_relocs * sizeof ( pe_rel->relocs[0] ) );
free ( pe_rel->relocs );
pe_rel->relocs = relocs;
}
/* Store relocation */
pe_rel->relocs[ pe_rel->used_relocs++ ] = reloc;
}
/**
* Process relocation record
*
* @v bfd BFD file
* @v section Section
* @v rel Relocation entry
* @v pe_reltab PE relocation table to fill in
*/
static void process_reloc ( bfd *bfd __unused, asection *section, arelent *rel,
struct pe_relocs **pe_reltab ) {
reloc_howto_type *howto = rel->howto;
asymbol *sym = *(rel->sym_ptr_ptr);
unsigned long offset = ( bfd_get_section_vma ( bfd, section ) +
rel->address - BASE_ADDRESS );
if ( bfd_is_abs_section ( sym->section ) ) {
/* Skip absolute symbols; the symbol value won't
* change when the object is loaded.
*/
} else if ( strcmp ( howto->name, "R_X86_64_64" ) == 0 ) {
/* Generate an 8-byte PE relocation */
generate_pe_reloc ( pe_reltab, offset, 8 );
} else if ( ( strcmp ( howto->name, "R_386_32" ) == 0 ) ||
( strcmp ( howto->name, "R_X86_64_32" ) == 0 ) ||
( strcmp ( howto->name, "R_X86_64_32S" ) == 0 ) ) {
/* Generate a 4-byte PE relocation */
generate_pe_reloc ( pe_reltab, offset, 4 );
} else if ( ( strcmp ( howto->name, "R_386_16" ) == 0 ) ||
( strcmp ( howto->name, "R_X86_64_16" ) == 0 ) ) {
/* Generate a 2-byte PE relocation */
generate_pe_reloc ( pe_reltab, offset, 2 );
} else if ( ( strcmp ( howto->name, "R_386_PC32" ) == 0 ) ||
( strcmp ( howto->name, "R_X86_64_PC32" ) == 0 ) ||
( strcmp ( howto->name, "R_X86_64_PLT32" ) == 0 ) ) {
/* Skip PC-relative relocations; all relative offsets
* remain unaltered when the object is loaded.
*/
} else {
eprintf ( "Unrecognised relocation type %s\n", howto->name );
exit ( 1 );
}
}
/**
* Calculate size of binary PE relocation table
*
* @v fh File handle
* @v pe_reltab PE relocation table
* @ret size Size of binary table
*/
static size_t output_pe_reltab ( int fd, struct pe_relocs *pe_reltab ) {
EFI_IMAGE_BASE_RELOCATION header;
struct pe_relocs *pe_rel;
static uint8_t pad[16];
unsigned int num_relocs;
size_t size;
size_t pad_size;
size_t total_size = 0;
for ( pe_rel = pe_reltab ; pe_rel ; pe_rel = pe_rel->next ) {
num_relocs = ( ( pe_rel->used_relocs + 1 ) & ~1 );
size = ( sizeof ( header ) +
( num_relocs * sizeof ( uint16_t ) ) );
pad_size = ( ( -size ) & ( sizeof ( pad ) - 1 ) );
size += pad_size;
header.VirtualAddress = pe_rel->start_rva;
header.SizeOfBlock = size;
xwrite ( fd, &header, sizeof ( header ) );
xwrite ( fd, pe_rel->relocs,
( num_relocs * sizeof ( uint16_t ) ) );
xwrite ( fd, pad, pad_size );
total_size += size;
}
return total_size;
}
/**
* Add relocation information
*
* @v elf_name ELF file name
* @v pe_name PE file name
*/
static void efireloc ( const char *elf_name, const char *pe_name ) {
struct pe_relocs *pe_reltab = NULL;
int fd;
EFI_IMAGE_DOS_HEADER *dos;
EFI_IMAGE_OPTIONAL_HEADER_UNION *nt;
EFI_IMAGE_DATA_DIRECTORY *data_dir;
EFI_IMAGE_SECTION_HEADER *pe_sections;
UINT32 *image_size;
bfd *bfd;
asymbol **symtab;
asection *section;
arelent **reltab;
arelent **rel;
size_t reloc_len;
/* Open the output file */
fd = open ( pe_name, O_RDWR );
if ( fd < 0 ) {
eprintf ( "Could not open %s: %s\n",
pe_name, strerror ( errno ) );
exit ( 1 );
}
/* Map the output file header */
dos = mmap ( NULL, PE_HEADER_LEN, ( PROT_READ | PROT_WRITE ),
MAP_SHARED, fd, 0 );
if ( ! dos ) {
eprintf ( "Could not mmap %s: %s\n",
pe_name, strerror ( errno ) );
exit ( 1 );
}
/* Locate the modifiable fields within the output file header */
nt = ( ( ( void * ) dos ) + dos->e_lfanew );
if ( nt->Pe32.FileHeader.Machine == EFI_IMAGE_MACHINE_IA32 ) {
image_size = &nt->Pe32.OptionalHeader.SizeOfImage;
data_dir = nt->Pe32.OptionalHeader.DataDirectory;
pe_sections = ( ( ( void * ) nt ) + sizeof ( nt->Pe32 ) );
} else if ( nt->Pe32Plus.FileHeader.Machine == EFI_IMAGE_MACHINE_X64 ) {
image_size = &nt->Pe32Plus.OptionalHeader.SizeOfImage;
data_dir = nt->Pe32Plus.OptionalHeader.DataDirectory;
pe_sections = ( ( ( void * ) nt ) + sizeof ( nt->Pe32Plus ) );
} else {
eprintf ( "Unrecognised machine type\n" );
exit ( 1 );
}
/* Open the input file */
bfd = open_input_bfd ( elf_name );
symtab = read_symtab ( bfd );
/* For each input section, create the appropriate relocation records */
for ( section = bfd->sections ; section ; section = section->next ) {
/* Discard non-allocatable sections */
if ( ! ( bfd_get_section_flags ( bfd, section ) & SEC_ALLOC ) )
continue;
/* Add relocations from this section */
reltab = read_reltab ( bfd, symtab, section );
for ( rel = reltab ; *rel ; rel++ )
process_reloc ( bfd, section, *rel, &pe_reltab );
free ( reltab );
}
/* Close input file */
bfd_close ( bfd );
/* Generate relocation section */
xlseek ( fd, 0, SEEK_END );
reloc_len = output_pe_reltab ( fd, pe_reltab );
/* Modify image header */
*image_size += reloc_len;
data_dir[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].Size = reloc_len;
pe_sections[RELOC_SECTION_INDEX].Misc.VirtualSize = reloc_len;
pe_sections[RELOC_SECTION_INDEX].SizeOfRawData = reloc_len;
/* Unmap output file header */
munmap ( dos, PE_HEADER_LEN );
/* Close output file */
xclose ( fd );
}
/**
* Print help
*
* @v program_name Program name
*/
static void print_help ( const char *program_name ) {
eprintf ( "Syntax: %s [-v|-q] infile outfile\n", program_name );
}
/**
* Parse command-line options
*
* @v argc Argument count
* @v argv Argument list
* @v opts Options structure to populate
*/
static int parse_options ( const int argc, char **argv,
struct options *opts ) {
int c;
while (1) {
int option_index = 0;
static struct option long_options[] = {
{ "help", 0, NULL, 'h' },
{ "verbose", 0, NULL, 'v' },
{ "quiet", 0, NULL, 'q' },
{ 0, 0, 0, 0 }
};
if ( ( c = getopt_long ( argc, argv, "hvq",
long_options,
&option_index ) ) == -1 ) {
break;
}
switch ( c ) {
case 'v':
opts->verbosity++;
break;
case 'q':
if ( opts->verbosity )
opts->verbosity--;
break;
case 'h':
print_help ( argv[0] );
exit ( 0 );
case '?':
default:
exit ( 2 );
}
}
return optind;
}
/**
* Main program
*
* @v argc Number of arguments
* @v argv Command-line arguments
* @ret rc Return status code
*/
int main ( int argc, char **argv ) {
struct options opts = {
.verbosity = 0,
};
int infile_index;
const char *infile;
const char *outfile;
/* Initialise libbfd */
bfd_init();
/* Parse command-line arguments */
infile_index = parse_options ( argc, argv, &opts );
if ( argc != ( infile_index + 2 ) ) {
print_help ( argv[0] );
exit ( 2 );
}
infile = argv[infile_index];
outfile = argv[infile_index + 1];
/* Add relocation information */
efireloc ( infile, outfile );
return 0;
}
#ifndef _ERRNO_H
#define _ERRNO_H
/*
* Copyright (C) 2012 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 (at your option) 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
*
* Error numbers
*
*/
#endif /* _ERRNO_H */
/*
* 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 (at your option) 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
*
* Huffman alphabets
*
*/
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "wimboot.h"
#include "huffman.h"
/**
* Transcribe binary value (for debugging)
*
* @v value Value
* @v bits Length of value (in bits)
* @ret string Transcribed value
*/
static const char * huffman_bin ( unsigned long value, unsigned int bits ) {
static char buf[ ( 8 * sizeof ( value ) ) + 1 /* NUL */ ];
char *out = buf;
/* Sanity check */
assert ( bits < sizeof ( buf ) );
/* Transcribe value */
while ( bits-- )
*(out++) = ( ( value & ( 1 << bits ) ) ? '1' : '0' );
*out = '\0';
return buf;
}
/**
* Dump Huffman alphabet (for debugging)
*
* @v alphabet Huffman alphabet
*/
static void __attribute__ (( unused ))
huffman_dump_alphabet ( struct huffman_alphabet *alphabet ) {
struct huffman_symbols *sym;
unsigned int bits;
unsigned int huf;
unsigned int i;
/* Dump symbol table for each utilised length */
for ( bits = 1 ; bits <= ( sizeof ( alphabet->huf ) /
sizeof ( alphabet->huf[0] ) ) ; bits++ ) {
sym = &alphabet->huf[ bits - 1 ];
if ( sym->freq == 0 )
continue;
huf = ( sym->start >> sym->shift );
DBG ( "Huffman length %d start \"%s\" freq %d:", bits,
huffman_bin ( huf, sym->bits ), sym->freq );
for ( i = 0 ; i < sym->freq ; i++ ) {
DBG ( " %03x", sym->raw[ huf + i ] );
}
DBG ( "\n" );
}
/* Dump quick lookup table */
DBG ( "Huffman quick lookup:" );
for ( i = 0 ; i < ( sizeof ( alphabet->lookup ) /
sizeof ( alphabet->lookup[0] ) ) ; i++ ) {
DBG ( " %d", ( alphabet->lookup[i] + 1 ) );
}
DBG ( "\n" );
}
/**
* Construct Huffman alphabet
*
* @v alphabet Huffman alphabet
* @v lengths Symbol length table
* @v count Number of symbols
* @ret rc Return status code
*/
int huffman_alphabet ( struct huffman_alphabet *alphabet,
uint8_t *lengths, unsigned int count ) {
struct huffman_symbols *sym;
unsigned int huf;
unsigned int cum_freq;
unsigned int bits;
unsigned int raw;
unsigned int adjustment;
unsigned int prefix;
int empty;
int complete;
/* Clear symbol table */
memset ( alphabet->huf, 0, sizeof ( alphabet->huf ) );
/* Count number of symbols with each Huffman-coded length */
empty = 1;
for ( raw = 0 ; raw < count ; raw++ ) {
bits = lengths[raw];
if ( bits ) {
alphabet->huf[ bits - 1 ].freq++;
empty = 0;
}
}
/* In the degenerate case of having no symbols (i.e. an unused
* alphabet), generate a trivial alphabet with exactly two
* single-bit codes. This allows callers to avoid having to
* check for this special case.
*/
if ( empty )
alphabet->huf[0].freq = 2;
/* Populate Huffman-coded symbol table */
huf = 0;
cum_freq = 0;
for ( bits = 1 ; bits <= ( sizeof ( alphabet->huf ) /
sizeof ( alphabet->huf[0] ) ) ; bits++ ) {
sym = &alphabet->huf[ bits - 1 ];
sym->bits = bits;
sym->shift = ( HUFFMAN_BITS - bits );
sym->start = ( huf << sym->shift );
sym->raw = &alphabet->raw[cum_freq];
huf += sym->freq;
if ( huf > ( 1U << bits ) ) {
DBG ( "Huffman alphabet has too many symbols with "
"lengths <=%d\n", bits );
return -1;
}
huf <<= 1;
cum_freq += sym->freq;
}
complete = ( huf == ( 1U << bits ) );
/* Populate raw symbol table */
for ( raw = 0 ; raw < count ; raw++ ) {
bits = lengths[raw];
if ( bits ) {
sym = &alphabet->huf[ bits - 1 ];
*(sym->raw++) = raw;
}
}
/* Adjust Huffman-coded symbol table raw pointers and populate
* quick lookup table.
*/
for ( bits = 1 ; bits <= ( sizeof ( alphabet->huf ) /
sizeof ( alphabet->huf[0] ) ) ; bits++ ) {
sym = &alphabet->huf[ bits - 1 ];
/* Adjust raw pointer */
sym->raw -= sym->freq; /* Reset to first symbol */
adjustment = ( sym->start >> sym->shift );
sym->raw -= adjustment; /* Adjust for quick indexing */
/* Populate quick lookup table */
for ( prefix = ( sym->start >> HUFFMAN_QL_SHIFT ) ;
prefix < ( 1 << HUFFMAN_QL_BITS ) ; prefix++ ) {
alphabet->lookup[prefix] = ( bits - 1 );
}
}
/* Check that there are no invalid codes */
if ( ! complete ) {
DBG ( "Huffman alphabet is incomplete\n" );
return -1;
}
return 0;
}
/**
* Get Huffman symbol set
*
* @v alphabet Huffman alphabet
* @v huf Raw input value (normalised to HUFFMAN_BITS bits)
* @ret sym Huffman symbol set
*/
struct huffman_symbols * huffman_sym ( struct huffman_alphabet *alphabet,
unsigned int huf ) {
struct huffman_symbols *sym;
unsigned int lookup_index;
/* Find symbol set for this length */
lookup_index = ( huf >> HUFFMAN_QL_SHIFT );
sym = &alphabet->huf[ alphabet->lookup[ lookup_index ] ];
while ( huf < sym->start )
sym--;
return sym;
}
#ifndef _HUFFMAN_H
#define _HUFFMAN_H
/*
* 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 (at your option) 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
*
* Huffman alphabets
*
*/
#include <stdint.h>
/** Maximum length of a Huffman symbol (in bits) */
#define HUFFMAN_BITS 16
/** Raw huffman symbol */
typedef uint16_t huffman_raw_symbol_t;
/** Quick lookup length for a Huffman symbol (in bits)
*
* This is a policy decision.
*/
#define HUFFMAN_QL_BITS 7
/** Quick lookup shift */
#define HUFFMAN_QL_SHIFT ( HUFFMAN_BITS - HUFFMAN_QL_BITS )
/** A Huffman-coded set of symbols of a given length */
struct huffman_symbols {
/** Length of Huffman-coded symbols (in bits) */
uint8_t bits;
/** Shift to normalise symbols of this length to HUFFMAN_BITS bits */
uint8_t shift;
/** Number of Huffman-coded symbols having this length */
uint16_t freq;
/** First symbol of this length (normalised to HUFFMAN_BITS bits)
*
* Stored as a 32-bit value to allow the value
* (1<<HUFFMAN_BITS ) to be used for empty sets of symbols
* longer than the maximum utilised length.
*/
uint32_t start;
/** Raw symbols having this length */
huffman_raw_symbol_t *raw;
};
/** A Huffman-coded alphabet */
struct huffman_alphabet {
/** Huffman-coded symbol set for each length */
struct huffman_symbols huf[HUFFMAN_BITS];
/** Quick lookup table */
uint8_t lookup[ 1 << HUFFMAN_QL_BITS ];
/** Raw symbols
*
* Ordered by Huffman-coded symbol length, then by symbol
* value. This field has a variable length.
*/
huffman_raw_symbol_t raw[0];
};
/**
* Get Huffman symbol length
*
* @v sym Huffman symbol set
* @ret len Length (in bits)
*/
static inline __attribute__ (( always_inline )) unsigned int
huffman_len ( struct huffman_symbols *sym ) {
return sym->bits;
}
/**
* Get Huffman symbol value
*
* @v sym Huffman symbol set
* @v huf Raw input value (normalised to HUFFMAN_BITS bits)
* @ret raw Raw symbol value
*/
static inline __attribute__ (( always_inline )) huffman_raw_symbol_t
huffman_raw ( struct huffman_symbols *sym, unsigned int huf ) {
return sym->raw[ huf >> sym->shift ];
}
extern int huffman_alphabet ( struct huffman_alphabet *alphabet,
uint8_t *lengths, unsigned int count );
extern struct huffman_symbols *
huffman_sym ( struct huffman_alphabet *alphabet, unsigned int huf );
#endif /* _HUFFMAN_H */
/*
* Copyright (C) 2012 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 (at your option) 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
*
* INT 13 emulation
*
*/
#include <string.h>
#include <stdio.h>
#include "wimboot.h"
#include "int13.h"
#include "vdisk.h"
/** Emulated drive number */
static int vdisk_drive;
/**
* Initialise emulation
*
* @ret drive Emulated drive number
*/
int initialise_int13 ( void ) {
/* Determine drive number */
vdisk_drive = ( 0x80 | INT13_DRIVE_COUNT++ );
DBG ( "Emulating drive %#02x\n", vdisk_drive );
return vdisk_drive;
}
/**
* INT 13, 08 - Get drive parameters
*
* @ret ch Low bits of maximum cylinder number
* @ret cl (bits 7:6) High bits of maximum cylinder number
* @ret cl (bits 5:0) Maximum sector number
* @ret dh Maximum head number
* @ret dl Number of drives
* @ret ah Status code
*/
static void int13_get_parameters ( struct bootapp_callback_params *params ) {
unsigned int max_cylinder = ( VDISK_CYLINDERS - 1 );
unsigned int max_head = ( VDISK_HEADS - 1 );
unsigned int max_sector = ( VDISK_SECTORS_PER_TRACK - 0 /* sic */ );
unsigned int num_drives;
unsigned int min_num_drives;
/* Calculate number of drives to report */
num_drives = INT13_DRIVE_COUNT;
min_num_drives = ( ( vdisk_drive & 0x7f ) + 1 );
if ( num_drives < min_num_drives )
num_drives = min_num_drives;
/* Fill in drive parameters */
params->ch = ( max_cylinder & 0xff );
params->cl = ( ( ( max_cylinder >> 8 ) << 6 ) | max_sector );
params->dh = max_head;
params->dl = num_drives;
DBG2 ( "Get parameters: C/H/S = %d/%d/%d, drives = %d\n",
( max_cylinder + 1 ), ( max_head + 1 ), max_sector, num_drives );
/* Success */
params->ah = 0;
}
/**
* INT 13, 15 - Get disk type
*
* @ret cx:dx Sector count
* @ret ah Type code
*/
static void int13_get_disk_type ( struct bootapp_callback_params *params ) {
uint32_t sector_count = VDISK_COUNT;
uint8_t drive_type = INT13_DISK_TYPE_HDD;
/* Fill in disk type */
params->cx = ( sector_count >> 16 );
params->dx = ( sector_count & 0xffff );
params->ah = drive_type;
DBG2 ( "Get disk type: sectors = %#08x, type = %d\n",
sector_count, drive_type );
}
/**
* INT 13, 41 - Extensions installation check
*
* @v bx 0x55aa
* @ret bx 0xaa55
* @ret cx Extensions API support bitmap
* @ret ah API version
*/
static void int13_extension_check ( struct bootapp_callback_params *params ) {
/* Fill in extension information */
params->bx = 0xaa55;
params->cx = INT13_EXTENSION_LINEAR;
params->ah = INT13_EXTENSION_VER_1_X;
DBG2 ( "Extensions installation check\n" );
}
/**
* INT 13, 48 - Get extended parameters
*
* @v ds:si Drive parameter table
* @ret ah Status code
*/
static void
int13_get_extended_parameters ( struct bootapp_callback_params *params ) {
struct int13_disk_parameters *disk_params;
/* Fill in extended parameters */
disk_params = REAL_PTR ( params->ds, params->si );
memset ( disk_params, 0, sizeof ( *disk_params ) );
disk_params->bufsize = sizeof ( *disk_params );
disk_params->flags = INT13_FL_DMA_TRANSPARENT;
disk_params->cylinders = VDISK_CYLINDERS;
disk_params->heads = VDISK_HEADS;
disk_params->sectors_per_track = VDISK_SECTORS_PER_TRACK;
disk_params->sectors = VDISK_COUNT;
disk_params->sector_size = VDISK_SECTOR_SIZE;
DBG2 ( "Get extended parameters: C/H/S = %d/%d/%d, sectors = %#08llx "
"(%d bytes)\n", disk_params->cylinders, disk_params->heads,
disk_params->sectors_per_track, disk_params->sectors,
disk_params->sector_size );
/* Success */
params->ah = 0;
}
/**
* INT 13, 42 - Extended read
*
* @v ds:si Disk address packet
* @ret ah Status code
*/
static void int13_extended_read ( struct bootapp_callback_params *params ) {
struct int13_disk_address *disk_address;
void *data;
/* Read from emulated disk */
disk_address = REAL_PTR ( params->ds, params->si );
data = REAL_PTR ( disk_address->buffer.segment,
disk_address->buffer.offset );
vdisk_read ( disk_address->lba, disk_address->count, data );
/* Success */
params->ah = 0;
}
/**
* Emulate INT 13 drive
*
* @v params Parameters
*/
void emulate_int13 ( struct bootapp_callback_params *params ) {
int command = params->ah;
int drive = params->dl;
int min_num_drives;
unsigned long eflags;
if ( drive == vdisk_drive ) {
/* Emulated drive - handle internally */
/* Populate eflags with a sensible starting value */
__asm__ ( "pushf\n\t"
"pop %0\n\t"
: "=r" ( eflags ) );
params->eflags = ( eflags & ~CF );
/* Handle command */
switch ( command ) {
case INT13_GET_PARAMETERS:
int13_get_parameters ( params );
break;
case INT13_GET_DISK_TYPE:
int13_get_disk_type ( params );
break;
case INT13_EXTENSION_CHECK:
int13_extension_check ( params );
break;
case INT13_GET_EXTENDED_PARAMETERS:
int13_get_extended_parameters ( params );
break;
case INT13_EXTENDED_READ:
int13_extended_read ( params );
break;
default:
DBG ( "Unrecognised INT 13,%02x\n", command );
params->eflags |= CF;
break;
}
} else {
/* Pass through command to underlying INT 13 */
call_interrupt ( params );
/* Modify drive count, if applicable */
if ( command == INT13_GET_PARAMETERS ) {
min_num_drives = ( ( vdisk_drive & 0x7f ) + 1 );
if ( params->dl < min_num_drives )
params->dl = min_num_drives;
}
}
}
#ifndef _INT13_H
#define _INT13_H
/*
* Copyright (C) 2012 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 (at your option) 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
*
* INT 13 emulation
*
*/
/** Construct a pointer from a real-mode segment:offset address */
#define REAL_PTR( segment, offset ) \
( ( void * ) ( intptr_t ) ( ( (segment) << 4 ) + offset ) )
/** Get drive parameters */
#define INT13_GET_PARAMETERS 0x08
/** Get disk type */
#define INT13_GET_DISK_TYPE 0x15
/** Extensions installation check */
#define INT13_EXTENSION_CHECK 0x41
/** Extended read */
#define INT13_EXTENDED_READ 0x42
/** Get extended drive parameters */
#define INT13_GET_EXTENDED_PARAMETERS 0x48
/** Operation completed successfully */
#define INT13_STATUS_SUCCESS 0x00
/** Invalid function or parameter */
#define INT13_STATUS_INVALID 0x01
/** Read error */
#define INT13_STATUS_READ_ERROR 0x04
/** Reset failed */
#define INT13_STATUS_RESET_FAILED 0x05
/** Write error */
#define INT13_STATUS_WRITE_ERROR 0xcc
/** No such drive */
#define INT13_DISK_TYPE_NONE 0x00
/** Floppy without change-line support */
#define INT13_DISK_TYPE_FDD 0x01
/** Floppy with change-line support */
#define INT13_DISK_TYPE_FDD_CL 0x02
/** Hard disk */
#define INT13_DISK_TYPE_HDD 0x03
/** Extended disk access functions supported */
#define INT13_EXTENSION_LINEAR 0x01
/** INT13 extensions version 1.x */
#define INT13_EXTENSION_VER_1_X 0x01
/** DMA boundary errors handled transparently */
#define INT13_FL_DMA_TRANSPARENT 0x01
/** BIOS drive counter */
#define INT13_DRIVE_COUNT ( *( ( ( uint8_t * ) REAL_PTR ( 0x40, 0x75 ) ) ) )
/** An INT 13 disk address packet */
struct int13_disk_address {
/** Size of the packet, in bytes */
uint8_t bufsize;
/** Reserved */
uint8_t reserved_a;
/** Block count */
uint8_t count;
/** Reserved */
uint8_t reserved_b;
/** Data buffer */
struct segoff buffer;
/** Starting block number */
uint64_t lba;
/** Data buffer (EDD 3.0+ only) */
uint64_t buffer_phys;
/** Block count (EDD 4.0+ only) */
uint32_t long_count;
/** Reserved */
uint32_t reserved_c;
} __attribute__ (( packed ));
/** INT 13 disk parameters */
struct int13_disk_parameters {
/** Size of this structure */
uint16_t bufsize;
/** Flags */
uint16_t flags;
/** Number of cylinders */
uint32_t cylinders;
/** Number of heads */
uint32_t heads;
/** Number of sectors per track */
uint32_t sectors_per_track;
/** Total number of sectors on drive */
uint64_t sectors;
/** Bytes per sector */
uint16_t sector_size;
} __attribute__ (( packed ));
extern int initialise_int13 ( void );
extern void emulate_int13 ( struct bootapp_callback_params *params );
#endif /* _INT13_H */
/*
* Copyright (C) 2006 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 <stddef.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include <wchar.h>
#include <ipxe/vsprintf.h>
/** @file */
#define CHAR_LEN 0 /**< "hh" length modifier */
#define SHORT_LEN 1 /**< "h" length modifier */
#define INT_LEN 2 /**< no length modifier */
#define LONG_LEN 3 /**< "l" length modifier */
#define LONGLONG_LEN 4 /**< "ll" length modifier */
#define SIZE_T_LEN 5 /**< "z" length modifier */
static uint8_t type_sizes[] = {
[CHAR_LEN] = sizeof ( char ),
[SHORT_LEN] = sizeof ( short ),
[INT_LEN] = sizeof ( int ),
[LONG_LEN] = sizeof ( long ),
[LONGLONG_LEN] = sizeof ( long long ),
[SIZE_T_LEN] = sizeof ( size_t ),
};
/**
* Use lower-case for hexadecimal digits
*
* Note that this value is set to 0x20 since that makes for very
* efficient calculations. (Bitwise-ORing with @c LCASE converts to a
* lower-case character, for example.)
*/
#define LCASE 0x20
/**
* Use "alternate form"
*
* For hexadecimal numbers, this means to add a "0x" or "0X" prefix to
* the number.
*/
#define ALT_FORM 0x02
/**
* Use zero padding
*
* Note that this value is set to 0x10 since that allows the pad
* character to be calculated as @c 0x20|(flags&ZPAD)
*/
#define ZPAD 0x10
/**
* Format a hexadecimal number
*
* @v end End of buffer to contain number
* @v num Number to format
* @v width Minimum field width
* @v flags Format flags
* @ret ptr End of buffer
*
* Fills a buffer in reverse order with a formatted hexadecimal
* number. The number will be zero-padded to the specified width.
* Lower-case and "alternate form" (i.e. "0x" prefix) flags may be
* set.
*
* There must be enough space in the buffer to contain the largest
* number that this function can format.
*/
static char * format_hex ( char *end, unsigned long long num, int width,
int flags ) {
char *ptr = end;
int case_mod = ( flags & LCASE );
int pad = ( ( flags & ZPAD ) | ' ' );
/* Generate the number */
do {
*(--ptr) = "0123456789ABCDEF"[ num & 0xf ] | case_mod;
num >>= 4;
} while ( num );
/* Pad to width */
while ( ( end - ptr ) < width )
*(--ptr) = pad;
/* Add "0x" or "0X" if alternate form specified */
if ( flags & ALT_FORM ) {
*(--ptr) = 'X' | case_mod;
*(--ptr) = '0';
}
return ptr;
}
/**
* Format a decimal number
*
* @v end End of buffer to contain number
* @v num Number to format
* @v width Minimum field width
* @v flags Format flags
* @ret ptr End of buffer
*
* Fills a buffer in reverse order with a formatted decimal number.
* The number will be space-padded to the specified width.
*
* There must be enough space in the buffer to contain the largest
* number that this function can format.
*/
static char * format_decimal ( char *end, signed long num, int width,
int flags ) {
char *ptr = end;
int negative = 0;
int zpad = ( flags & ZPAD );
int pad = ( zpad | ' ' );
/* Generate the number */
if ( num < 0 ) {
negative = 1;
num = -num;
}
do {
*(--ptr) = '0' + ( num % 10 );
num /= 10;
} while ( num );
/* Add "-" if necessary */
if ( negative && ( ! zpad ) )
*(--ptr) = '-';
/* Pad to width */
while ( ( end - ptr ) < width )
*(--ptr) = pad;
/* Add "-" if necessary */
if ( negative && zpad )
*ptr = '-';
return ptr;
}
/**
* Print character via a printf context
*
* @v ctx Context
* @v c Character
*
* Call's the printf_context::handler() method and increments
* printf_context::len.
*/
static inline void cputchar ( struct printf_context *ctx, unsigned int c ) {
ctx->handler ( ctx, c );
++ctx->len;
}
/**
* Write a formatted string to a printf context
*
* @v ctx Context
* @v fmt Format string
* @v args Arguments corresponding to the format string
* @ret len Length of formatted string
*/
size_t vcprintf ( struct printf_context *ctx, const char *fmt, va_list args ) {
int flags;
int width;
uint8_t *length;
char *ptr;
char tmp_buf[32]; /* 32 is enough for all numerical formats.
* Insane width fields could overflow this buffer. */
wchar_t *wptr;
/* Initialise context */
ctx->len = 0;
for ( ; *fmt ; fmt++ ) {
/* Pass through ordinary characters */
if ( *fmt != '%' ) {
cputchar ( ctx, *fmt );
continue;
}
fmt++;
/* Process flag characters */
flags = 0;
for ( ; ; fmt++ ) {
if ( *fmt == '#' ) {
flags |= ALT_FORM;
} else if ( *fmt == '0' ) {
flags |= ZPAD;
} else {
/* End of flag characters */
break;
}
}
/* Process field width */
width = 0;
for ( ; ; fmt++ ) {
if ( ( ( unsigned ) ( *fmt - '0' ) ) < 10 ) {
width = ( width * 10 ) + ( *fmt - '0' );
} else {
break;
}
}
/* We don't do floating point */
/* Process length modifier */
length = &type_sizes[INT_LEN];
for ( ; ; fmt++ ) {
if ( *fmt == 'h' ) {
length--;
} else if ( *fmt == 'l' ) {
length++;
} else if ( *fmt == 'z' ) {
length = &type_sizes[SIZE_T_LEN];
} else {
break;
}
}
/* Process conversion specifier */
ptr = tmp_buf + sizeof ( tmp_buf ) - 1;
*ptr = '\0';
wptr = NULL;
if ( *fmt == 'c' ) {
if ( length < &type_sizes[LONG_LEN] ) {
cputchar ( ctx, va_arg ( args, unsigned int ) );
} else {
wchar_t wc;
size_t len;
wc = va_arg ( args, wint_t );
len = wcrtomb ( tmp_buf, wc, NULL );
tmp_buf[len] = '\0';
ptr = tmp_buf;
}
} else if ( *fmt == 's' ) {
if ( length < &type_sizes[LONG_LEN] ) {
ptr = va_arg ( args, char * );
} else {
wptr = va_arg ( args, wchar_t * );
}
if ( ( ptr == NULL ) && ( wptr == NULL ) )
ptr = "<NULL>";
} else if ( *fmt == 'p' ) {
intptr_t ptrval;
ptrval = ( intptr_t ) va_arg ( args, void * );
ptr = format_hex ( ptr, ptrval, width,
( ALT_FORM | LCASE ) );
} else if ( ( *fmt & ~0x20 ) == 'X' ) {
unsigned long long hex;
flags |= ( *fmt & 0x20 ); /* LCASE */
if ( *length >= sizeof ( unsigned long long ) ) {
hex = va_arg ( args, unsigned long long );
} else if ( *length >= sizeof ( unsigned long ) ) {
hex = va_arg ( args, unsigned long );
} else {
hex = va_arg ( args, unsigned int );
}
ptr = format_hex ( ptr, hex, width, flags );
} else if ( ( *fmt == 'd' ) || ( *fmt == 'i' ) ){
signed long decimal;
if ( *length >= sizeof ( signed long ) ) {
decimal = va_arg ( args, signed long );
} else {
decimal = va_arg ( args, signed int );
}
ptr = format_decimal ( ptr, decimal, width, flags );
} else {
*(--ptr) = *fmt;
}
/* Write out conversion result */
if ( wptr == NULL ) {
for ( ; *ptr ; ptr++ ) {
cputchar ( ctx, *ptr );
}
} else {
for ( ; *wptr ; wptr++ ) {
size_t len = wcrtomb ( tmp_buf, *wptr, NULL );
for ( ptr = tmp_buf ; len-- ; ptr++ ) {
cputchar ( ctx, *ptr );
}
}
}
}
return ctx->len;
}
/** Context used by vsnprintf() and friends */
struct sputc_context {
struct printf_context ctx;
/** Buffer for formatted string (used by printf_sputc()) */
char *buf;
/** Buffer length (used by printf_sputc()) */
size_t max_len;
};
/**
* Write character to buffer
*
* @v ctx Context
* @v c Character
*/
static void printf_sputc ( struct printf_context *ctx, unsigned int c ) {
struct sputc_context * sctx =
container_of ( ctx, struct sputc_context, ctx );
if ( ctx->len < sctx->max_len )
sctx->buf[ctx->len] = c;
}
/**
* Write a formatted string to a buffer
*
* @v buf Buffer into which to write the string
* @v size Size of buffer
* @v fmt Format string
* @v args Arguments corresponding to the format string
* @ret len Length of formatted string
*
* If the buffer is too small to contain the string, the returned
* length is the length that would have been written had enough space
* been available.
*/
int vsnprintf ( char *buf, size_t size, const char *fmt, va_list args ) {
struct sputc_context sctx;
size_t len;
size_t end;
/* Hand off to vcprintf */
sctx.ctx.handler = printf_sputc;
sctx.buf = buf;
sctx.max_len = size;
len = vcprintf ( &sctx.ctx, fmt, args );
/* Add trailing NUL */
if ( size ) {
end = size - 1;
if ( len < end )
end = len;
buf[end] = '\0';
}
return len;
}
/**
* Write a formatted string to a buffer
*
* @v buf Buffer into which to write the string
* @v size Size of buffer
* @v fmt Format string
* @v ... Arguments corresponding to the format string
* @ret len Length of formatted string
*/
int snprintf ( char *buf, size_t size, const char *fmt, ... ) {
va_list args;
int i;
va_start ( args, fmt );
i = vsnprintf ( buf, size, fmt, args );
va_end ( args );
return i;
}
/**
* Version of vsnprintf() that accepts a signed buffer size
*
* @v buf Buffer into which to write the string
* @v size Size of buffer
* @v fmt Format string
* @v args Arguments corresponding to the format string
* @ret len Length of formatted string
*/
int vssnprintf ( char *buf, ssize_t ssize, const char *fmt, va_list args ) {
/* Treat negative buffer size as zero buffer size */
if ( ssize < 0 )
ssize = 0;
/* Hand off to vsnprintf */
return vsnprintf ( buf, ssize, fmt, args );
}
/**
* Version of vsnprintf() that accepts a signed buffer size
*
* @v buf Buffer into which to write the string
* @v size Size of buffer
* @v fmt Format string
* @v ... Arguments corresponding to the format string
* @ret len Length of formatted string
*/
int ssnprintf ( char *buf, ssize_t ssize, const char *fmt, ... ) {
va_list args;
int len;
/* Hand off to vssnprintf */
va_start ( args, fmt );
len = vssnprintf ( buf, ssize, fmt, args );
va_end ( args );
return len;
}
/**
* Write character to console
*
* @v ctx Context
* @v c Character
*/
static void printf_putchar ( struct printf_context *ctx __unused,
unsigned int c ) {
putchar ( c );
}
/**
* Write a formatted string to the console
*
* @v fmt Format string
* @v args Arguments corresponding to the format string
* @ret len Length of formatted string
*/
int vprintf ( const char *fmt, va_list args ) {
struct printf_context ctx;
/* Hand off to vcprintf */
ctx.handler = printf_putchar;
return vcprintf ( &ctx, fmt, args );
}
/**
* Write a formatted string to the console.
*
* @v fmt Format string
* @v ... Arguments corresponding to the format string
* @ret len Length of formatted string
*/
int printf ( const char *fmt, ... ) {
va_list args;
int i;
va_start ( args, fmt );
i = vprintf ( fmt, args );
va_end ( args );
return i;
}
#ifndef _IPXE_VSPRINTF_H
#define _IPXE_VSPRINTF_H
/** @file
*
* printf() and friends
*
* Etherboot's printf() functions understand the following subset of
* the standard C printf()'s format specifiers:
*
* - Flag characters
* - '#' - Alternate form (i.e. "0x" prefix)
* - '0' - Zero-pad
* - Field widths
* - Length modifiers
* - 'hh' - Signed / unsigned char
* - 'h' - Signed / unsigned short
* - 'l' - Signed / unsigned long
* - 'll' - Signed / unsigned long long
* - 'z' - Signed / unsigned size_t
* - Conversion specifiers
* - 'd' - Signed decimal
* - 'x','X' - Unsigned hexadecimal
* - 'c' - Character
* - 's' - String
* - 'p' - Pointer
*
* Hexadecimal numbers are always zero-padded to the specified field
* width (if any); decimal numbers are always space-padded. Decimal
* long longs are not supported.
*
*/
FILE_LICENCE ( GPL2_OR_LATER );
#include <stdint.h>
#include <stdarg.h>
#include <stdio.h>
/**
* A printf context
*
* Contexts are used in order to be able to share code between
* vprintf() and vsnprintf(), without requiring the allocation of a
* buffer for vprintf().
*/
struct printf_context {
/**
* Character handler
*
* @v ctx Context
* @v c Character
*
* This method is called for each character written to the
* formatted string.
*/
void ( * handler ) ( struct printf_context *ctx, unsigned int c );
/** Length of formatted string
*
* When handler() is called, @len will be set to the number of
* characters written so far (i.e. zero for the first call to
* handler()).
*/
size_t len;
};
extern size_t vcprintf ( struct printf_context *ctx, const char *fmt,
va_list args );
extern int vssnprintf ( char *buf, ssize_t ssize, const char *fmt,
va_list args );
extern int __attribute__ (( format ( printf, 3, 4 ) ))
ssnprintf ( char *buf, ssize_t ssize, const char *fmt, ... );
#endif /* _IPXE_VSPRINTF_H */
/*
* Copyright (C) 2012 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 (at your option) 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
*
* LZNT1 decompression
*
*/
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include "wimboot.h"
#include "lznt1.h"
/**
* Decompress LZNT1-compressed data block
*
* @v data Compressed data
* @v limit Length of compressed data up to end of block
* @v offset Starting offset within compressed data
* @v block Decompression buffer for this block, or NULL
* @ret out_len Length of decompressed block, or negative error
*/
static ssize_t lznt1_block ( const void *data, size_t limit, size_t offset,
void *block ) {
const uint16_t *tuple;
const uint8_t *copy_src;
uint8_t *copy_dest = block;
size_t copy_len;
size_t block_out_len = 0;
unsigned int split = 12;
unsigned int next_threshold = 16;
unsigned int tag_bit = 0;
unsigned int tag = 0;
while ( offset != limit ) {
/* Extract tag */
if ( tag_bit == 0 ) {
tag = *( ( uint8_t * ) ( data + offset ) );
offset++;
if ( offset == limit )
break;
}
/* Calculate copy source and length */
if ( tag & 1 ) {
/* Compressed value */
if ( offset + sizeof ( *tuple ) > limit ) {
DBG ( "LZNT1 compressed value overrun at "
"%#zx\n", offset );
return -1;
}
tuple = ( data + offset );
offset += sizeof ( *tuple );
copy_len = LZNT1_VALUE_LEN ( *tuple, split );
block_out_len += copy_len;
if ( copy_dest ) {
copy_src = ( copy_dest -
LZNT1_VALUE_OFFSET ( *tuple,
split ) );
while ( copy_len-- )
*(copy_dest++) = *(copy_src++);
}
} else {
/* Uncompressed value */
copy_src = ( data + offset );
if ( copy_dest )
*(copy_dest++) = *copy_src;
offset++;
block_out_len++;
}
/* Update split, if applicable */
while ( block_out_len > next_threshold ) {
split--;
next_threshold <<= 1;
}
/* Move to next value */
tag >>= 1;
tag_bit = ( ( tag_bit + 1 ) % 8 );
}
return block_out_len;
}
/**
* Decompress LZNT1-compressed data
*
* @v data Compressed data
* @v len Length of compressed data
* @v buf Decompression buffer, or NULL
* @ret out_len Length of decompressed data, or negative error
*/
ssize_t lznt1_decompress ( const void *data, size_t len, void *buf ) {
const uint16_t *header;
const uint8_t *end;
size_t offset = 0;
ssize_t out_len = 0;
size_t block_len;
size_t limit;
void *block;
ssize_t block_out_len;
while ( offset != len ) {
/* Check for end marker */
if ( ( offset + sizeof ( *end ) ) == len ) {
end = ( data + offset );
if ( *end == 0 )
break;
}
/* Extract block header */
if ( ( offset + sizeof ( *header ) ) > len ) {
DBG ( "LZNT1 block header overrun at %#zx\n", offset );
return -1;
}
header = ( data + offset );
offset += sizeof ( *header );
/* Process block */
block_len = LZNT1_BLOCK_LEN ( *header );
if ( LZNT1_BLOCK_COMPRESSED ( *header ) ) {
/* Compressed block */
DBG2 ( "LZNT1 compressed block %#zx+%#zx\n",
offset, block_len );
limit = ( offset + block_len );
block = ( buf ? ( buf + out_len ) : NULL );
block_out_len = lznt1_block ( data, limit, offset,
block );
if ( block_out_len < 0 )
return block_out_len;
offset += block_len;
out_len += block_out_len;
} else {
/* Uncompressed block */
if ( ( offset + block_len ) > len ) {
DBG ( "LZNT1 uncompressed block overrun at "
"%#zx+%#zx\n", offset, block_len );
return -1;
}
DBG2 ( "LZNT1 uncompressed block %#zx+%#zx\n",
offset, block_len );
if ( buf ) {
memcpy ( ( buf + out_len ), ( data + offset ),
block_len );
}
offset += block_len;
out_len += block_len;
}
}
return out_len;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment