diff --git a/com32/elflink/ldlinux/adv.c b/com32/elflink/ldlinux/adv.c index 78695471d..cf02d1294 100644 --- a/com32/elflink/ldlinux/adv.c +++ b/com32/elflink/ldlinux/adv.c @@ -32,24 +32,9 @@ */ #include -#include -#include -#include +#include -void *__syslinux_adv_ptr; -size_t __syslinux_adv_size; - -extern void adv_init(void); void __syslinux_init(void) { - static com32sys_t reg; - - /* Initialize the ADV structure */ - reg.eax.w[0] = 0x0025; - __intcall(0x22, ®, NULL); - - reg.eax.w[0] = 0x001c; - __intcall(0x22, ®, ®); - __syslinux_adv_ptr = MK_PTR(reg.es, reg.ebx.w[0]); - __syslinux_adv_size = reg.ecx.w[0]; + firmware->adv_ops->init(); } diff --git a/com32/elflink/ldlinux/advwrite.c b/com32/elflink/ldlinux/advwrite.c index 4152eea5d..95a311a8b 100644 --- a/com32/elflink/ldlinux/advwrite.c +++ b/com32/elflink/ldlinux/advwrite.c @@ -32,14 +32,9 @@ */ #include -#include -#include +#include int syslinux_adv_write(void) { - static com32sys_t reg; - - reg.eax.w[0] = 0x001d; - __intcall(0x22, ®, ®); - return (reg.eflags.l & EFLAGS_CF) ? -1 : 0; + return firmware->adv_ops->write(); } diff --git a/com32/include/syslinux/firmware.h b/com32/include/syslinux/firmware.h index d52e34a03..3877d624a 100644 --- a/com32/include/syslinux/firmware.h +++ b/com32/include/syslinux/firmware.h @@ -19,6 +19,11 @@ struct input_ops { char (*getchar)(void); }; +struct adv_ops { + void (*init)(void); + int (*write)(void); +}; + struct firmware { void (*init)(void); int (*scan_memory)(scan_memory_callback_t, void *); @@ -30,10 +35,12 @@ struct firmware { char *(*get_config_file_name)(void); void (*get_serial_console_info)(uint16_t *, uint16_t *, uint16_t *); bool (*ipappend_strings)(char **, int *); + struct adv_ops *adv_ops; }; extern struct firmware *firmware; extern void syslinux_register_bios(void); +extern void init(void); #endif /* _SYSLINUX_FIRMWARE_H */ diff --git a/core/bios.c b/core/bios.c index 8725edad4..447268d67 100644 --- a/core/bios.c +++ b/core/bios.c @@ -63,6 +63,36 @@ bool bios_ipappend_strings(char **list, int *count) extern char *bios_get_config_file_name(void); extern void bios_get_serial_console_info(uint16_t *, uint16_t *, uint16_t *); +void *__syslinux_adv_ptr; +size_t __syslinux_adv_size; + +void bios_adv_init(void) +{ + static com32sys_t reg; + + reg.eax.w[0] = 0x0025; + __intcall(0x22, ®, ®); + + reg.eax.w[0] = 0x001c; + __intcall(0x22, ®, ®); + __syslinux_adv_ptr = MK_PTR(reg.es, reg.ebx.w[0]); + __syslinux_adv_size = reg.ecx.w[0]; +} + +int bios_adv_write(void) +{ + static com32sys_t reg; + + reg.eax.w[0] = 0x001d; + __intcall(0x22, ®, ®); + return (reg.eflags.l & EFLAGS_CF) ? -1 : 0; +} + +struct adv_ops bios_adv_ops = { + .init = bios_adv_init, + .write = bios_adv_write, +}; + struct firmware bios_fw = { .init = bios_init, .scan_memory = bios_scan_memory, @@ -74,6 +104,7 @@ struct firmware bios_fw = { .ipappend_strings = bios_ipappend_strings, .get_config_file_name = bios_get_config_file_name, .get_serial_console_info = bios_get_serial_console_info, + .adv_ops = &bios_adv_ops, }; void syslinux_register_bios(void) diff --git a/core/elflink/advwrite.c b/core/elflink/advwrite.c deleted file mode 100644 index 4152eea5d..000000000 --- a/core/elflink/advwrite.c +++ /dev/null @@ -1,45 +0,0 @@ -/* ----------------------------------------------------------------------- * - * - * Copyright 2007-2008 H. Peter Anvin - All Rights Reserved - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom - * the Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall - * be included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - * ----------------------------------------------------------------------- */ - -/* - * syslinux/advwrite.c - * - * Write back the ADV - */ - -#include -#include -#include - -int syslinux_adv_write(void) -{ - static com32sys_t reg; - - reg.eax.w[0] = 0x001d; - __intcall(0x22, ®, ®); - return (reg.eflags.l & EFLAGS_CF) ? -1 : 0; -} diff --git a/core/elflink/setadv.c b/core/elflink/setadv.c deleted file mode 100644 index 40f00a4e6..000000000 --- a/core/elflink/setadv.c +++ /dev/null @@ -1,116 +0,0 @@ -/* ----------------------------------------------------------------------- * - * - * Copyright 2007-2008 H. Peter Anvin - All Rights Reserved - * Copyright 2009 Intel Corporation; author: H. Peter Anvin - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom - * the Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall - * be included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - * ----------------------------------------------------------------------- */ - -/* - * syslinux/setadv.c - * - * (Over)write a data item in the auxilliary data vector. To - * delete an item, set its length to zero. - * - * Return 0 on success, -1 on error, and set errno. - * - * NOTE: Data is not written to disk unless - * syslinux_adv_write() is called. - */ - -#include -#include -#include -#include -#include -#include - -int syslinux_setadv(int tag, size_t size, const void *data) -{ - uint8_t *p, *advtmp; - size_t rleft, left; - - if ((unsigned)tag - 1 > 254) { - errno = EINVAL; - return -1; /* Impossible tag value */ - } - - if (size > 255) { - errno = ENOSPC; /* Max 255 bytes for a data item */ - return -1; - } - - rleft = left = syslinux_adv_size(); - p = advtmp = alloca(left); - memcpy(p, syslinux_adv_ptr(), left); /* Make working copy */ - - while (rleft >= 2) { - uint8_t ptag = p[0]; - size_t plen = p[1] + 2; - - if (ptag == ADV_END) - break; - - if (ptag == tag) { - /* Found our tag. Delete it. */ - - if (plen >= rleft) { - /* Entire remainder is our tag */ - break; - } - memmove(p, p + plen, rleft - plen); - rleft -= plen; /* Fewer bytes to read, but not to write */ - } else { - /* Not our tag */ - if (plen > rleft) - break; /* Corrupt tag (overrun) - overwrite it */ - - left -= plen; - rleft -= plen; - p += plen; - } - } - - /* Now (p, left) reflects the position to write in and how much space - we have for our data. */ - - if (size) { - if (left < size + 2) { - errno = ENOSPC; /* Not enough space for data */ - return -1; - } - - *p++ = tag; - *p++ = size; - memcpy(p, data, size); - p += size; - left -= size + 2; - } - - memset(p, 0, left); - - /* If we got here, everything went OK, commit the write to low memory */ - memcpy(syslinux_adv_ptr(), advtmp, syslinux_adv_size()); - - return 0; -} diff --git a/efi/adv.c b/efi/adv.c new file mode 100644 index 000000000..3dec3cc86 --- /dev/null +++ b/efi/adv.c @@ -0,0 +1,362 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2007-2008 H. Peter Anvin - All Rights Reserved + * Copyright 2012 Intel Corporation; author: H. Peter Anvin + * Chandramouli Narayanan + * + * 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, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * adv.c + * + * Core ADV I/O + * Code consolidated from libinstaller/adv*.c and core/adv.inc with the + * addition of EFI support + * + * Return 0 on success, -1 on error, and set errno. + * + */ +#define _GNU_SOURCE + +#include "adv.h" + +#define IS_SYSLINUX /* remove this: test build only */ + +unsigned char syslinux_adv[2 * ADV_SIZE]; + +static void cleanup_adv(unsigned char *advbuf) +{ + int i; + uint32_t csum; + + /* Make sure both copies agree, and update the checksum */ + *(uint32_t *)advbuf = ADV_MAGIC1; + + csum = ADV_MAGIC2; + for (i = 8; i < ADV_SIZE - 4; i += 4) + csum -= *(uint32_t *)(advbuf + i); + + *(uint32_t *)(advbuf + 4) = csum; + *(uint32_t *)(advbuf + ADV_SIZE - 4) = ADV_MAGIC3; + + memcpy(advbuf + ADV_SIZE, advbuf, ADV_SIZE); +} + +void syslinux_reset_adv(unsigned char *advbuf) +{ + /* Create an all-zero ADV */ + memset(advbuf + 2 * 4, 0, ADV_LEN); + cleanup_adv(advbuf); +} + +static int adv_consistent(const unsigned char *p) +{ + int i; + uint32_t csum; + + if (*(uint32_t *)p != ADV_MAGIC1 || + *(uint32_t *)(p + ADV_SIZE - 4) != ADV_MAGIC3) + return 0; + + csum = 0; + for (i = 4; i < ADV_SIZE - 4; i += 4) + csum += *(uint32_t *)(p + i); + + return csum == ADV_MAGIC2; +} + +/* + * Verify that an in-memory ADV is consistent, making the copies consistent. + * If neither copy is OK, return -1 and call syslinux_reset_adv(). + */ +int syslinux_validate_adv(unsigned char *advbuf) +{ + if (adv_consistent(advbuf + 0 * ADV_SIZE)) { + memcpy(advbuf + ADV_SIZE, advbuf, ADV_SIZE); + return 0; + } else if (adv_consistent(advbuf + 1 * ADV_SIZE)) { + memcpy(advbuf, advbuf + ADV_SIZE, ADV_SIZE); + return 0; + } else { + syslinux_reset_adv(advbuf); + return -1; + } +} + +/* + * Read the ADV from an existing instance, or initialize if invalid. + * Returns -1 on fatal errors, 0 if ADV is okay, 1 if the ADV is + * invalid, and 2 if the file does not exist. + */ + +/* make_filespec + * Take the ASCII pathname and filename and concatenate them + * into an allocated memory space as unicode file specification string. + * The path and cfg ASCII strings are assumed to be null-terminated. + * For EFI, the separation character in the path name is '\' + * and therefore it is assumed that the file spec uses '\' as separation char + * + * The function returns + * 0 if successful and fspec is a valid allocated CHAR16 pointer + * Caller is responsible to free up the allocated filespec string + * -1 otherwise + * + */ +static int make_filespec(CHAR16 **fspec, const char *path, const char *cfg) +{ + CHAR16 *p; + int size, append; + + /* allocate size for a CHAR16 string */ + size = sizeof(CHAR16) * (strlena((CHAR8 *)path)+strlena((CHAR8 *)cfg)+2); /* including null */ + *fspec = malloc(size); + if (!*fspec) return -1; + + append = path[strlena((CHAR8 *)path) - 1] != '\\'; + for (p = *fspec; *path; path++, p++) + *p = (CHAR16)*path; + /* append the separation character to the path if need be */ + if (append) *p++ = (CHAR16)'\\'; + for (; *cfg; cfg++, p++) + *p = (CHAR16)*cfg; + *p = (CHAR16)CHAR_NULL; + + return 0; +} + + +/* TODO: + * set_attributes() and clear_attributes() are supported for VFAT only + */ +int read_adv(const char *path, const char *cfg) +{ + CHAR16 *file; + EFI_FILE_HANDLE fd; + EFI_FILE_INFO st; + int err = 0; + int rv; + + rv = make_filespec(&file, path, cfg); + if (rv < 0 || !file) { + efi_perror(L"read_adv"); + return -1; + } + + /* TBD: Not sure if EFI accepts the attribute read only + * even if an existing file is opened for read access + */ + fd = efi_open(file, EFI_FILE_MODE_READ); + if (!fd) { + if (efi_errno != EFI_NOT_FOUND) { + err = -1; + } else { + syslinux_reset_adv(syslinux_adv); + err = 2; /* Nonexistence is not a fatal error */ + } + } else if (!efi_fstat(fd, &st)) { + err = -1; + } else if (st.FileSize < 2 * ADV_SIZE) { + /* Too small to be useful */ + syslinux_reset_adv(syslinux_adv); + err = 0; /* Nothing to read... */ + } else if (efi_xpread(fd, syslinux_adv, 2 * ADV_SIZE, + st.FileSize - 2 * ADV_SIZE) != 2 * ADV_SIZE) { + err = -1; + } else { + /* We got it... maybe? */ + err = syslinux_validate_adv(syslinux_adv) ? 1 : 0; + } + + if (err < 0) + efi_perror(file); + if (fd) + efi_close(fd); + free(file); + + return err; +} + +/* For EFI platform, initialize ADV by opening ldlinux.sys or extlinux.sys + * as configured and return the primary (adv0) and alternate (adv1) + * data into caller's buffer. File remains open for subsequent + * operations. This routine is to be called from comboot + * vector. Currently only IS_SYSLINUX or IS_EXTLINUX is supported + * + * TODO: + * 1. Need to set the path to ldlinux.sys or extlinux.sys; currently null + * 2. What if there are errors? + */ +void efi_adv_init(void) +{ + char *name; + int rv; + int err = 0; + unsigned char *advbuf = syslinux_adv; + EFI_FILE_HANDLE fd; /* handle to ldlinux.sys or extlinux.sys */ + CHAR16 *file; + EFI_FILE_INFO st, xst; + +#if defined IS_SYSLINUX + name = SYSLINUX_FILE; +#elif defined IS_EXTLINUX + name = EXTLINUX_FILE; +#else + #error "IS_SYSLINUX or IS_EXTLINUX must be specified to build ADV" +#endif + /* FIXME: No path defined to syslinux/extlinux file */ + rv = make_filespec(&file, "", name); + if (rv < 0 || !file) { + efi_errno = EFI_OUT_OF_RESOURCES; + efi_perror(L"efi_adv_init:"); + return; + } + + fd = efi_open(file, EFI_FILE_MODE_READ); + if (fd == (EFI_FILE_HANDLE)NULL) { + err = -1; + efi_printerr(L"efi_adv_init: Unable to open file %s\n", file); + } else if (efi_fstat(fd, &st)) { + err = -1; + efi_printerr(L"efi_adv_init: Unable to get info for file %s\n", file); + } else if (st.FileSize < 2 * ADV_SIZE) { + /* Too small to be useful */ + err = -2; + efi_printerr(L"efi_adv_init: File %s size too small to be useful %s\n", file); + } else if (efi_xpread(fd, advbuf, 2 * ADV_SIZE, + st.FileSize - 2 * ADV_SIZE) != 2 * ADV_SIZE) { + err = -1; + efi_printerr(L"efi_adv_init: Error reading ADV data from file %s\n", file); + } else { + /* We got it... maybe? */ + __syslinux_adv_ptr = &syslinux_adv[8]; /* skip head, csum */ + __syslinux_adv_size = ADV_LEN; + + err = syslinux_validate_adv(advbuf) ? -2 : 0; + if (!err) { + /* Got a good one*/ + efi_clear_attributes(fd); + + /* Need to re-open read-write */ + efi_close(fd); + /* There is no SYNC attribute with EFI open */ + fd = efi_open(file, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE); + /* on error, only explicit comparison with null handle works */ + if (fd == (EFI_FILE_HANDLE)NULL) { + err = -1; + efi_perror(L"efi_adv_init:"); + } else if (efi_fstat(fd, &xst) || xst.FileSize != st.FileSize) { + /* device/inode info don't exist in the EFI file info structure */ + efi_perror(L"efi_adv_init: file status error/mismatch"); + err = -2; + } + /* TODO: Do we need to set attributes of the sys file? */ + } + } + if (file) + free(file); + if (fd != 0) + efi_close(fd); + /* TODO: In case of errors, we could set efi_errno to EFI_LOAD_ERROR + * to mean that ADV could not be loaded up + */ +} + +/* For EFI platform, write 2 * ADV_SIZE data to the file opened + * at ADV initialization. (i.e ldlinux.sys or extlinux.sys). + * + * TODO: + * 1. Validate assumption: write back to file from __syslinux_adv_ptr + * 2. What if there errors? + * 3. Do we need to set the attributes of the sys file? + * + */ +int efi_adv_write(void) +{ + char *name; + unsigned char advtmp[2 * ADV_SIZE]; + unsigned char *advbuf = syslinux_adv; + int rv; + int err = 0; + EFI_FILE_HANDLE fd; /* handle to ldlinux.sys or extlinux.sys */ + CHAR16 *file; + EFI_FILE_INFO st, xst; + +#if defined IS_SYSLINUX + name = SYSLINUX_FILE; +#elif defined IS_EXTLINUX + name = EXTLINUX_FILE; +#else + #error "IS_SYSLINUX or IS_EXTLINUX must be specified to build ADV" +#endif + rv = make_filespec(&file, "", name); + if (rv < 0 || !file) { + efi_errno = EFI_OUT_OF_RESOURCES; + efi_perror(L"efi_adv_write:"); + return -1; + } + + fd = efi_open(file, EFI_FILE_MODE_READ); + if (fd == (EFI_FILE_HANDLE)NULL) { + err = -1; + efi_printerr(L"efi_adv_write: Unable to open file %s\n", file); + } else if (efi_fstat(fd, &st)) { + err = -1; + efi_printerr(L"efi_adv_write: Unable to get info for file %s\n", file); + } else if (st.FileSize < 2 * ADV_SIZE) { + /* Too small to be useful */ + err = -2; + efi_printerr(L"efi_adv_write: File size too small to be useful for file %s\n", file); + } else if (efi_xpread(fd, advtmp, 2 * ADV_SIZE, + st.FileSize - 2 * ADV_SIZE) != 2 * ADV_SIZE) { + err = -1; + efi_printerr(L"efi_adv_write: Error reading ADV data from file %s\n", file); + } else { + cleanup_adv(advbuf); + err = syslinux_validate_adv(advbuf) ? -2 : 0; + + if (!err) { + /* Got a good one, write our own ADV here */ + efi_clear_attributes(fd); + + /* Need to re-open read-write */ + efi_close(fd); + /* There is no SYNC attribute with EFI open */ + fd = efi_open(file, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE); + if (fd == (EFI_FILE_HANDLE)NULL) { + err = -1; + } else if (efi_fstat(fd, &xst) || xst.FileSize != st.FileSize) { + efi_perror(L"efi_adv_write: file status error/mismatch"); + err = -2; + } + /* Write our own version ... */ + if (efi_xpwrite(fd, advbuf, 2 * ADV_SIZE, + st.FileSize - 2 * ADV_SIZE) != 2 * ADV_SIZE) { + err = -1; + efi_printerr(L"efi_adv_write: Error write ADV data to file %s\n", file); + } + if (!err) { + efi_sync(fd); + efi_set_attributes(fd); + } + } + } + + if (err == -2) + efi_printerr(L"%s: cannot write auxilliary data (need --update)?\n", + file); + else if (err == -1) + efi_perror(L"efi_adv_write:"); + + if (fd) + efi_close(fd); + if (file) + free(file); + + return err; +} diff --git a/efi/adv.h b/efi/adv.h new file mode 100644 index 000000000..e8ccb352b --- /dev/null +++ b/efi/adv.h @@ -0,0 +1,29 @@ +#ifndef _H_EFI_ADV_ +#define _H_EFI_ADV_ + +#include "efi.h" +#include "fio.h" +#include + +/* ADV information */ +#define ADV_SIZE 512 /* Total size */ +#define ADV_LEN (ADV_SIZE-3*4) /* Usable data size */ +/* Currently, one of IS_SYSLINUX or IS_EXTLINUX must be defined for ADV */ +#define SYSLINUX_FILE "ldlinux.sys" +#define EXTLINUX_FILE "extlinux.sys" + +#define ADV_MAGIC1 0x5a2d2fa5 /* Head signature */ +#define ADV_MAGIC2 0xa3041767 /* Total checksum */ +#define ADV_MAGIC3 0xdd28bf64 /* Tail signature */ + +extern unsigned char syslinux_adv[2 * ADV_SIZE]; +extern void *__syslinux_adv_ptr; +extern ssize_t __syslinux_adv_size; + +/* TODO: Revisit to ensure if these functions need to be exported */ +void syslinux_reset_adv(unsigned char *advbuf); +int syslinux_validate_adv(unsigned char *advbuf); +int read_adv(const char *path, const char *cfg); +int write_adv(const char *path, const char *cfg); + +#endif diff --git a/efi/efi.h b/efi/efi.h index ac5ca3fd0..7e53fb2aa 100644 --- a/efi/efi.h +++ b/efi/efi.h @@ -2,8 +2,10 @@ #define _SYSLINUX_EFI_H #include -#include +#include /* needed for off_t */ +//#include /* avoid redefinition of __STDC_VERSION__ */ #include #include +#include #endif /* _SYSLINUX_EFI_H */ diff --git a/efi/fio.c b/efi/fio.c new file mode 100644 index 000000000..f56cd5b47 --- /dev/null +++ b/efi/fio.c @@ -0,0 +1,283 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2007-2008 H. Peter Anvin - All Rights Reserved + * Copyright 2012 Intel Corporation; author: H. Peter Anvin + * Chandramouli Narayanan + * + * 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, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* Miscellaneous functions for UEFI support + * We assume that EFI library initialization has completed + * and we have access to the global EFI exported variables + * + */ +#include "efi.h" +#include "fio.h" + +/* Variables that need to be exported + * efi_errno - maintains the errors from EFI calls to display error messages. + */ +EFI_STATUS efi_errno = EFI_SUCCESS; + +/* Locals + * vol_root - handle to the root device for file operations + */ +static EFI_FILE_HANDLE vol_root; + +/* Table of UEFI error messages to be indexed with the EFI errno + * Update error message list as needed + */ +static CHAR16 *uefi_errmsg[] = { + L"EFI_UNDEFINED", /* should not get here */ + L"EFI_LOAD_ERROR", + L"EFI_INVALID_PARAMETER", + L"EFI_UNSUPPORTED", + L"EFI_BAD_BUFFER_SIZE", + L"EFI_BUFFER_TOO_SMALL", + L"EFI_NOT_READY", + L"EFI_DEVICE_ERROR", + L"EFI_WRITE_PROTECTED", + L"EFI_OUT_OF_RESOURCES", + L"EFI_VOLUME_CORRUPTED", + L"EFI_VOLUME_FULL", + L"EFI_NO_MEDIA", + L"EFI_MEDIA_CHANGED", + L"EFI_NOT_FOUND", + L"EFI_ACCESS_DENIED", + L"EFI_NO_RESPONSE", + L"EFI_NO_MAPPING", + L"EFI_TIMEOUT", + L"EFI_NOT_STARTED", + L"EFI_ALREADY_STARTED", + L"EFI_ABORTED", + L"EFI_ICMP_ERROR", + L"EFI_TFTP_ERROR", + L"EFI_PROTOCOL_ERROR" +}; + +static UINTN nerrs = sizeof(uefi_errmsg)/sizeof(CHAR16 *); + + +/* Generic write error message; there is no gnu lib api to write to StdErr + * For now, everything goes ConOut + */ +void efi_printerr( + CHAR16 *fmt, + ... + ) +{ + va_list args; + va_start (args, fmt); + VPrint (fmt, args); + va_end (args); +} + +/* Simple console logger of efi-specific error messages. It uses + * gnu-efi library Print function to do the job. + */ + +void efi_perror(CHAR16 *prog) +{ + /* Ensure that the err number lies within range + * Beware: unsigned comparisons fail on efi, signed comparisons work + */ + if (EFI_ERROR(efi_errno) && (INTN)efi_errno < (INTN)nerrs) + efi_printerr(L"%s: %s\n", prog, uefi_errmsg[efi_errno]); +} + +/* Write to UEFI ConOut */ +void efi_printout( + CHAR16 *fmt, + ... + ) +{ + va_list args; + va_start (args, fmt); + VPrint (fmt, args); + va_end (args); +} + +/* IMPORTANT: + * efi_setvol_root() needs to be called from efi main. + * The rest of the ADV support relies on the file i/o environment + * setup here. In order to use the EFI file support, we need + * to set up the volume root. Subsequent file operations need the root to + * access the interface routines. + * + */ + +EFI_STATUS efi_set_volroot(EFI_HANDLE device_handle) +{ + vol_root = LibOpenRoot(device_handle); + if (!vol_root) { + return EFI_DEVICE_ERROR; + } + return EFI_SUCCESS; +} + +/* File operations using EFI runtime services */ + +/* Open the file using EFI runtime service + * Opening a file in EFI requires a handle to the device + * root in order to use the interface to the file operations supported by UEFI. + * For now, assume device volume root handle from the loaded image + * + * Return a valid handle if open succeeded and null otherwise. + * UEFI returns a bogus handle on error, so return null handle on error. + * + * TODO: + * 1. Validate the assumption about the root device + * 2. Can EFI open a file with full path name specification? + * 3. Look into gnu-efi helper functions for dealing with device path/file path + * 4. Consider utilizing EFI file open attributes. + * 5. In EFI, file attributes can be specified only at the time of creation. + * How do we support the equivalent of set_attributes() and clear_attributes() + */ +EFI_FILE_HANDLE efi_open(CHAR16 *file, UINT64 mode) +{ + /* initialize with NULL handle since EFI open returns bogus */ + EFI_FILE_HANDLE fd = NULL; + + ASSERT(vol_root); + + /* Note that the attributes parameter is none for now */ + efi_errno = uefi_call_wrapper(vol_root->Open, + 5, + vol_root, + &fd, + file, + mode, + 0); + return fd; +} + +/* + * read/write wrapper functions for UEFI + * + * Read or write the specified number of bytes starting at the + * offset specified. + * + * Returns: + * number of bytes read/written on success + * -1 on error + */ +/* Wrapper function to read from a file */ +size_t efi_xpread(EFI_FILE_HANDLE fd, void *buf, size_t count, off_t offset) +{ + ASSERT(fd); + efi_errno = uefi_call_wrapper(fd->SetPosition, + 2, + fd, + offset); + if (EFI_ERROR(efi_errno)) return -1; + efi_errno = uefi_call_wrapper(fd->Read, + 3, + fd, + &count, + buf); + if (EFI_ERROR(efi_errno)) return -1; + return count; +} + +/* Wrapper function to write */ +size_t efi_xpwrite(EFI_FILE_HANDLE fd, void *buf, size_t count, off_t offset) +{ + ASSERT(fd); + efi_errno = uefi_call_wrapper(fd->SetPosition, + 2, + fd, + offset); + if (EFI_ERROR(efi_errno)) return -1; + efi_errno = uefi_call_wrapper(fd->Write, + 3, + fd, + &count, + buf); + if (EFI_ERROR(efi_errno)) return -1; + return count; +} + +/* For an open handle, return the generic file info excluding + * the variable-length filename in the EFI_FILE_INFO structure. + */ +int efi_fstat(EFI_FILE_HANDLE fd, EFI_FILE_INFO *st) +{ + EFI_FILE_INFO *finfo; + + ASSERT(fd); + finfo = LibFileInfo(fd); + if (finfo) { + uefi_call_wrapper(BS->CopyMem, 3, (VOID *)st, (VOID *)finfo, SIZE_OF_EFI_FILE_INFO); + FreePool(finfo); + return 0; + } + /* gnu-efi lib does not return EFI status; export a generic device error for now */ + efi_errno = EFI_DEVICE_ERROR; + return -1; +} + +/* set/clear_attributes() + * Currently handles only VFAT filesystem + * TODO: + * 1. Assumes VFAT file system. + * 2. How do we support other file systems? + */ +void efi_set_attributes(EFI_FILE_HANDLE fd) +{ + EFI_FILE_INFO *finfo; + + ASSERT(fd); + finfo = LibFileInfo(fd); + if (finfo) { + /* Hidden+System+Readonly */ + finfo->Attribute = EFI_FILE_READ_ONLY|EFI_FILE_HIDDEN|EFI_FILE_SYSTEM; + efi_errno = uefi_call_wrapper(fd->SetInfo, + 4, + fd, + &GenericFileInfo, + finfo->Size, + finfo); + FreePool(finfo); + } else efi_errno = EFI_NOT_FOUND; +} + +void efi_clear_attributes(EFI_FILE_HANDLE fd) +{ + EFI_FILE_INFO *finfo; + + ASSERT(fd); + finfo = LibFileInfo(fd); + if (finfo) { + finfo->Attribute = 0; /* no attributes */ + efi_errno = uefi_call_wrapper(fd->SetInfo, + 4, + fd, + &GenericFileInfo, + finfo->Size, + finfo); + FreePool(finfo); + } else efi_errno = EFI_NOT_FOUND; +} + +/* Implement the sync operation using the EFI Flush file operation*/ +void efi_sync(EFI_FILE_HANDLE fd) +{ + ASSERT(fd); + efi_errno = uefi_call_wrapper(fd->Flush, 1, fd); + return; +} + +/* Close the file */ +void efi_close(EFI_FILE_HANDLE fd) +{ + + ASSERT(fd); + efi_errno = uefi_call_wrapper(fd->Close, 1, fd); + return; +} diff --git a/efi/fio.h b/efi/fio.h new file mode 100644 index 000000000..65fff8df0 --- /dev/null +++ b/efi/fio.h @@ -0,0 +1,43 @@ +#ifndef _H_EFI_FIO_ +#define _H_EFI_FIO_ + +/* + * Friendly interfaces for EFI file I/O and various EFI support functions + */ + +/* MAX_EFI_ARGS - command line args for EFI executable + * WS(c16) - check for CHAR16 white space + */ +#define MAX_EFI_ARGS 64 +#define WS(c16) (c16 == L' ' || c16 == CHAR_TAB) + +/* VPrint is not in export declarations in gnu-efi lib yet + * although it is a global function; declare it here + */ +extern UINTN +VPrint ( + IN CHAR16 *fmt, + va_list args + ); + +extern EFI_STATUS efi_errno; + +void efi_memcpy(unsigned char *dst, unsigned char *src, size_t len); +void efi_memmove(unsigned char *dst, unsigned char *src, size_t len); +void efi_memset(unsigned char *dst, unsigned char val, size_t len); +void *efi_alloc(int size); +void efi_free(void *ptr); +void efi_perror(CHAR16 *str); +void efi_printerr(IN CHAR16 *fmt, ...); +void efi_printout(IN CHAR16 *fmt, ...); +EFI_STATUS efi_set_volroot(EFI_HANDLE device_handle); +EFI_FILE_HANDLE efi_open(CHAR16 *file, UINT64 mode); +void efi_close(EFI_FILE_HANDLE fd); +void efi_sync(EFI_FILE_HANDLE fd); +size_t efi_xpread(EFI_FILE_HANDLE fd, void *buf, size_t count, off_t offset); +size_t efi_xpwrite(EFI_FILE_HANDLE fd, void *buf, size_t count, off_t offset); +int efi_fstat(EFI_FILE_HANDLE fd, EFI_FILE_INFO *st); +void efi_set_attributes(EFI_FILE_HANDLE fd); +void efi_clear_attributes(EFI_FILE_HANDLE fd); + +#endif diff --git a/efi/main.c b/efi/main.c index 5b92acd26..4afa7d476 100644 --- a/efi/main.c +++ b/efi/main.c @@ -6,6 +6,7 @@ #include #include "efi.h" +#include "fio.h" char KernelName[FILENAME_MAX]; uint16_t PXERetry; @@ -52,8 +53,9 @@ void __cdecl core_farcall(uint32_t c, const com32sys_t *a, com32sys_t *b) { } -void *__syslinux_adv_ptr; /* definitely needs to die: is in ldlinux now */ -size_t __syslinux_adv_size; /* definitely needs to die: is in ldlinux now */ +struct firmware *firmware = NULL; +void *__syslinux_adv_ptr; +size_t __syslinux_adv_size; char core_xfer_buf[65536]; struct iso_boot_info { uint32_t pvd; /* LBA of primary volume descriptor */ @@ -260,6 +262,14 @@ bool efi_ipappend_strings(char **list, int *count) *list = (char *)IPAppends; } +extern void efi_adv_init(void); +extern int efi_adv_write(void); + +struct adv_ops efi_adv_ops = { + .init = efi_adv_init, + .write = efi_adv_write, +}; + extern struct disk *efi_disk_init(com32sys_t *); extern void serialcfg(uint16_t *, uint16_t *, uint16_t *); @@ -272,6 +282,7 @@ struct firmware efi_fw = { .get_config_file_name = efi_get_config_file_name, .get_serial_console_info = serialcfg, .ipappend_strings = efi_ipappend_strings, + .adv_ops = &efi_adv_ops, }; static inline void syslinux_register_efi(void) @@ -302,10 +313,18 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *table) status = uefi_call_wrapper(BS->HandleProtocol, 3, image, &LoadedImageProtocol, (void **)&info); if (status != EFI_SUCCESS) { - printf("Failed to lookup LoadedImageProtocol\n"); + Print(L"Failed to lookup LoadedImageProtocol\n"); goto out; } + /* Use device handle to set up the volume root to proceed with ADV init */ + if (EFI_ERROR(efi_set_volroot(info->DeviceHandle))) { + Print(L"Failed to locate root device to prep for file operations & ADV initialization\n"); + goto out; + } + + /* TODO: once all errors are captured in efi_errno, bail out if necessary */ + /* XXX figure out what file system we're on */ fs_init(ops, info->DeviceHandle); load_env32();