blob: d9ad3b990557e20b3e878f2a45dfe497129edfc7 [file] [log] [blame]
/*
* Copyright (C) 2021 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.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <string.h>
#include <errno.h>
#include <ipxe/timer.h>
#include <ipxe/image.h>
#include <ipxe/netdevice.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_utils.h>
#include <ipxe/efi/efi_autoexec.h>
#include <ipxe/efi/mnpnet.h>
#include <usr/imgmgmt.h>
#include <usr/sync.h>
/** @file
*
* EFI autoexec script
*
*/
/** Timeout for autoexec script downloads */
#define EFI_AUTOEXEC_TIMEOUT ( 2 * TICKS_PER_SEC )
/** Autoexec script image name */
#define EFI_AUTOEXEC_NAME "autoexec.ipxe"
/** An EFI autoexec script loader */
struct efi_autoexec_loader {
/** Required protocol GUID */
EFI_GUID *protocol;
/**
* Load autoexec script
*
* @v handle Handle on which protocol was found
* @v image Image to fill in
* @ret rc Return status code
*/
int ( * load ) ( EFI_HANDLE handle, struct image **image );
};
/**
* Load autoexec script from filesystem
*
* @v handle Simple filesystem protocol handle
* @v image Image to fill in
* @ret rc Return status code
*/
static int efi_autoexec_filesystem ( EFI_HANDLE handle, struct image **image ) {
EFI_HANDLE device = efi_loaded_image->DeviceHandle;
int rc;
/* Check that we were loaded from a filesystem */
if ( handle != device ) {
DBGC ( device, "EFI %s is not the file system handle\n",
efi_handle_name ( device ) );
return -ENOTTY;
}
/* Try loading from loaded image directory, if supported */
if ( ( rc = imgacquire ( "file:" EFI_AUTOEXEC_NAME,
EFI_AUTOEXEC_TIMEOUT, image ) ) == 0 )
return 0;
/* Try loading from root directory, if supported */
if ( ( rc = imgacquire ( "file:/" EFI_AUTOEXEC_NAME,
EFI_AUTOEXEC_TIMEOUT, image ) ) == 0 )
return 0;
return rc;
}
/**
* Load autoexec script via temporary network device
*
* @v handle Managed network protocol service binding handle
* @v image Image to fill in
* @ret rc Return status code
*/
static int efi_autoexec_network ( EFI_HANDLE handle, struct image **image ) {
EFI_HANDLE device = efi_loaded_image->DeviceHandle;
struct net_device *netdev;
int rc;
/* Create temporary network device */
if ( ( rc = mnptemp_create ( handle, &netdev ) ) != 0 ) {
DBGC ( device, "EFI %s could not create net device: %s\n",
efi_handle_name ( device ), strerror ( rc ) );
goto err_create;
}
/* Open network device */
if ( ( rc = netdev_open ( netdev ) ) != 0 ) {
DBGC ( device, "EFI %s could not open net device: %s\n",
efi_handle_name ( device ), strerror ( rc ) );
goto err_open;
}
/* Attempt download */
rc = imgacquire ( EFI_AUTOEXEC_NAME, EFI_AUTOEXEC_TIMEOUT, image );
if ( rc != 0 ) {
DBGC ( device, "EFI %s could not download %s: %s\n",
efi_handle_name ( device ), EFI_AUTOEXEC_NAME,
strerror ( rc ) );
}
/* Ensure network exchanges have completed */
sync ( EFI_AUTOEXEC_TIMEOUT );
err_open:
mnptemp_destroy ( netdev );
err_create:
return rc;
}
/** Autoexec script loaders */
static struct efi_autoexec_loader efi_autoexec_loaders[] = {
{
.protocol = &efi_simple_file_system_protocol_guid,
.load = efi_autoexec_filesystem,
},
{
.protocol = &efi_managed_network_service_binding_protocol_guid,
.load = efi_autoexec_network,
},
};
/**
* Load autoexec script
*
* @ret rc Return status code
*/
int efi_autoexec_load ( void ) {
EFI_HANDLE device = efi_loaded_image->DeviceHandle;
EFI_HANDLE handle;
struct efi_autoexec_loader *loader;
struct image *image;
unsigned int i;
int rc;
/* Use first applicable loader */
for ( i = 0 ; i < ( sizeof ( efi_autoexec_loaders ) /
sizeof ( efi_autoexec_loaders[0] ) ) ; i ++ ) {
/* Locate required protocol for this loader */
loader = &efi_autoexec_loaders[i];
if ( ( rc = efi_locate_device ( device, loader->protocol,
&handle, 0 ) ) != 0 ) {
DBGC ( device, "EFI %s found no %s: %s\n",
efi_handle_name ( device ),
efi_guid_ntoa ( loader->protocol ),
strerror ( rc ) );
continue;
}
DBGC ( device, "EFI %s found %s on ",
efi_handle_name ( device ),
efi_guid_ntoa ( loader->protocol ) );
DBGC ( device, "%s\n", efi_handle_name ( handle ) );
/* Try loading */
if ( ( rc = loader->load ( handle, &image ) ) != 0 )
return rc;
/* Discard zero-length images */
if ( ! image->len ) {
DBGC ( device, "EFI %s discarding zero-length %s\n",
efi_handle_name ( device ), image->name );
unregister_image ( image );
return -ENOENT;
}
DBGC ( device, "EFI %s loaded %s (%zd bytes)\n",
efi_handle_name ( device ), image->name, image->len );
return 0;
}
return -ENOENT;
}