/* * Copyright (C) 2001-2003 Hewlett-Packard Co. * Contributed by Stephane Eranian * * Copyright (C) 2001 Silicon Graphics, Inc. * Contributed by Brent Casavant * * This file is part of the ELILO, the EFI Linux boot loader. * * ELILO 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, or (at your option) * any later version. * * ELILO 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 ELILO; see the file COPYING. If not, write to the Free * Software Foundation, 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Please check out the elilo.txt for complete documentation on how * to use this program. */ #include #include #include "elilo.h" #define TENTH_SEC 1000000 /* 1/10th second in 100ns unit */ #define READ_BLOCK_SIZE (4*EFI_PAGE_SIZE) /* block size for read_file */ #define is_cr(k) (((k)==CHAR_LINEFEED)||((k)==CHAR_CARRIAGE_RETURN)) #define CHAR_SPACE L' ' static INTN read_keypress(EFI_INPUT_KEY *key) { return systab->ConIn->ReadKeyStroke(systab->ConIn, key); } EFI_STATUS check_abort(VOID) { EFI_INPUT_KEY key; return read_keypress(&key); } inline VOID reset_input(VOID) { systab->ConIn->Reset(systab->ConIn, 1); } #if 0 INTN wait_keypress_abort(VOID) { SIMPLE_INPUT_INTERFACE *conin = systab->ConIn; EFI_INPUT_KEY key; EFI_STATUS status; reset_input(); Print(L"Hit ENTER to continue or ANY other key to cancel"); /* cleanup buffer first */ while (conin->ReadKeyStroke(conin, &key) == EFI_SUCCESS); while ((status=conin->ReadKeyStroke(conin, &key)) == EFI_NOT_READY ); if (EFI_ERROR(status)) return ELILO_LOAD_ERROR; Print(L"\n"); return is_cr(key.UnicodeChar) ? ELILO_LOAD_SUCCESS: ELILO_BOOT_ABORTED; } #endif /* * wait for timeout to expire or keypress * Return: * 0 : timeout expired * 1 : a key was pressed (still input stream to process) * -1: an error occured */ INTN wait_timeout(UINTN timeout) { EFI_STATUS status; EFI_EVENT timer; EFI_EVENT list[2]; UINTN idx; if (timeout == 0) return 0; /* Create a timeout timer */ status = BS->CreateEvent(EVT_TIMER, 0, NULL, NULL, &timer); if (EFI_ERROR(status)) { ERR_PRT((L" waitkey CreateEvent failed %r", status)); return -1; } /* In 100ns increments */ status = BS->SetTimer(timer, TimerPeriodic, TENTH_SEC); if (EFI_ERROR(status)) { ERR_PRT((L"waitkey SetTimer failed %r", status)); return -1; } list[0] = timer; list[1] = systab->ConIn->WaitForKey; do { status = BS->WaitForEvent(2, list, &idx); if (EFI_ERROR(status)) { ERR_PRT((L"waitkey WaitForEvent failed %r", status)); return -1; } } while (timeout-- && idx == 0); /* * SetTimer(timer, TimerCancel, 0) is causing problems on IA-32 and gcc3 * I do not know why it dies with EFI12.35. So let's fake a key stroke. */ status = BS->SetTimer(timer, TimerCancel, 0); if (EFI_ERROR(status)) { ERR_PRT((L"waitkey SetTimer(TimerCancel) failed %r", status)); return -1; } BS->CloseEvent(timer); return idx ? 1 : 0; } INTN argify(CHAR16 *buf, UINTN len, CHAR16 **argv) { UINTN i=0, j=0; CHAR16 *p = buf; if (buf == 0) { argv[0] = NULL; return 0; } /* len represents the number of bytes, not the number of 16 bytes chars */ len = len >> 1; /* * Here we use CHAR_NULL as the terminator rather than the length * because it seems like the EFI shell return rather bogus values for it. * Apparently, we are guaranteed to find the '\0' character in the buffer * where the real input arguments stop, so we use it instead. */ for(;;) { while (buf[i] == CHAR_SPACE && buf[i] != CHAR_NULL && i < len) i++; if (buf[i] == CHAR_NULL || i == len) goto end; p = buf+i; i++; while (buf[i] != CHAR_SPACE && buf[i] != CHAR_NULL && i < len) i++; argv[j++] = p; if (buf[i] == CHAR_NULL) goto end; buf[i] = CHAR_NULL; if (i == len) goto end; i++; if (j == MAX_ARGS-1) { ERR_PRT((L"too many arguments (%d) truncating", j)); goto end; } } end: #if 0 if (i != len) { ERR_PRT((L"ignoring trailing %d characters on command line", len-i)); } #endif argv[j] = NULL; return j; } VOID unargify(CHAR16 **argv, CHAR16 **args) { if ( *argv == 0 ) { *args = L""; return; } *args = *argv; while ( argv[1] ) { (*argv)[StrLen(*argv)] = CHAR_SPACE; argv++; } } VOID split_args(CHAR16 *buffer, CHAR16 *kname, CHAR16 *args) { CHAR16 *tmp; /* find beginning of kernel name */ while (*buffer && *buffer == CHAR_SPACE) buffer++; tmp = buffer; /* scan through kernel name */ while (*buffer && *buffer != CHAR_SPACE) buffer++; if (*buffer) { *buffer++ = CHAR_NULL; StrCpy(kname, tmp); } /* skip space between kernel and args */ while (*buffer && *buffer == CHAR_SPACE) buffer++; StrCpy(args, buffer); } INTN read_file(UINTN fd, UINTN total_size, CHAR8 *buffer) { INTN size, j=0; EFI_STATUS status; CHAR16 helicopter[4] = { L'|' , L'/' , L'-' , L'\\' }; INTN ret = ELILO_LOAD_SUCCESS; UINTN sum = 0; /* * We load by chunks rather than a single big read because * early versions of EFI had troubles loading files * from floppies in a single big request. Breaking * the read down into chunks of 4KB fixed that * problem. While this problem has been fixed, we still prefer * this method because it tells us whether or not we're making * forward progress. */ while (total_size > 0) { size = total_size < READ_BLOCK_SIZE? total_size : READ_BLOCK_SIZE; status = fops_read(fd, buffer, &size); if (EFI_ERROR(status)) { ERR_PRT((L"read_file failed %r", status)); return ELILO_LOAD_ERROR; } sum += size; Print(L"%c\b",helicopter[j++%4]); buffer += size; total_size -= size; if (check_abort() == EFI_SUCCESS) { ret = ELILO_LOAD_ABORTED; break; } } return ret; } INTN get_memmap(mmap_desc_t *desc) { #define ELILO_MEMMAP_SIZE_DEFAULT EFI_PAGE_SIZE #define ELILO_MEMMAP_INC (sizeof(EFI_MEMORY_DESCRIPTOR)<<1) EFI_STATUS status; desc->map_size = ELILO_MEMMAP_SIZE_DEFAULT; for(;;) { desc->md = (EFI_MEMORY_DESCRIPTOR *)alloc(desc->map_size, EfiLoaderData); if (desc->md == NULL) { ERR_PRT((L"failed to allocate memory map buffer")); return -1; } status = (*BS->GetMemoryMap)(&desc->map_size, desc->md, &desc->cookie, &desc->desc_size, &desc->desc_version); if (status == EFI_SUCCESS) break; free(desc->md); if (status != EFI_BUFFER_TOO_SMALL) { ERR_PRT((L"failed to obtain memory map %r")); return -1; } desc->map_size += ELILO_MEMMAP_INC; } DBG_PRT((L"final get_memmap map_size=%ld", desc->map_size)); return 0; } #if 0 INTN get_memmap(mmap_desc_t *desc) { EFI_STATUS status; /* will get the right size in return */ desc->map_size = 0; status = BS->GetMemoryMap(&desc->map_size, desc->md, &desc->cookie, &desc->desc_size, &desc->desc_version); if (status != EFI_BUFFER_TOO_SMALL) return -1; desc->md = (EFI_MEMORY_DESCRIPTOR *)alloc(desc->map_size, EfiLoaderData); if (desc->md == NULL) { ERR_PRT((L"failed to allocate memory map buffer")); return -1; } status = BS->GetMemoryMap(&desc->map_size, desc->md, &desc->cookie, &desc->desc_size, &desc->desc_version); if (EFI_ERROR(status)) { ERR_PRT((L"failed to obtain memory map %d: %r", desc->map_size, status)); free(desc->md); return -1; } DBG_PRT((L"final get_memmap map_size=%d", desc->map_size)); return 0; } #endif VOID free_memmap(mmap_desc_t *desc) { if (desc->md) { free(desc->md); desc->md = NULL; } } VOID print_memmap(mmap_desc_t *desc) { EFI_MEMORY_DESCRIPTOR *md; UINTN desc_size; VOID *p; VOID *md_end; INT8 printed; UINTN ntypes; CHAR16* str; static CHAR16 *memtypes[]={ L"ReservedMemoryType", L"LoaderCode", L"LoaderData", L"BootServicesCode", L"BootServicesData", L"RuntimeServicesCode", L"RuntimeServicesData", L"ConventionalMemory", L"UnusableMemory", L"ACPIReclaimMemory", L"ACPIMemoryNVS", L"MemoryMappedIO", L"MemoryMappedIOPortSpace", L"PalCode" }; md_end = ((VOID *)desc->md)+desc->map_size; desc_size = desc->desc_size; ntypes = sizeof(memtypes)/sizeof(CHAR16 *); for(p = desc->md; p < md_end; p += desc_size) { md = p; str = md->Type < ntypes ? memtypes[md->Type] : L"Unknown"; Print(L"%24s %lx-%lx %8lx", str, md->PhysicalStart, md->PhysicalStart+(md->NumberOfPages<NumberOfPages); printed=0; #define P_FLG(f) { \ Print(L" %s %s", printed ? L"|":L"", f); \ printed=1; \ } if (md->Attribute & EFI_MEMORY_UC) { P_FLG(L"UC"); } if (md->Attribute & EFI_MEMORY_WC) { P_FLG(L"WC"); } if (md->Attribute & EFI_MEMORY_WT) { P_FLG(L"WT"); } if (md->Attribute & EFI_MEMORY_WB) { P_FLG(L"WB"); } if (md->Attribute & EFI_MEMORY_UCE) { P_FLG(L"UCE"); } if (md->Attribute & EFI_MEMORY_WP) { P_FLG(L"WP"); } if (md->Attribute & EFI_MEMORY_RP) { P_FLG(L"RP"); } if (md->Attribute & EFI_MEMORY_XP) { P_FLG(L"XP"); } if (md->Attribute & EFI_MEMORY_RUNTIME) { P_FLG(L"RT"); } Print(L"\n"); } } INTN find_kernel_memory(VOID* low_addr, VOID* max_addr, UINTN alignment, VOID** start) { #define HIGHEST_ADDR (VOID*)(~0) mmap_desc_t mdesc; EFI_MEMORY_DESCRIPTOR *md; UINT64 size; VOID *p, *addr; VOID *desc_end, *md_end, *best_addr = HIGHEST_ADDR; /* * first get up-to-date memory map * * XXX: is there a danger of not seeing the latest version if interrupted * during our scan ? * */ if (get_memmap(&mdesc) == -1) { ERR_PRT((L"find_kernel_memory :GetMemoryMap() failed")); return -1; } desc_end = ((VOID *)mdesc.md) + mdesc.map_size; size = max_addr - low_addr; /* * Find memory which covers the desired range */ for(p = mdesc.md; p < desc_end; p += mdesc.desc_size) { md = p; /* * restrict to decent memory types. * * the EFI memory map report where memory is and how it is currently used * using types. * * EfiLoaderData which is used by the AllocatePages() cannot be used * here because it may hold some valid information. Same thing for most * of the memory types with the exception of EfiConventional which * can be assumed as being free to use. */ if (md->Type != EfiConventionalMemory) continue; /* * compute aligned address and upper boundary for range */ md_end = (VOID*)(md->PhysicalStart + md->NumberOfPages * EFI_PAGE_SIZE); addr = (VOID*)ROUNDUP(md->PhysicalStart, alignment); /* * need to check if: * - aligned address still in the range * - the range [addr-addr+size) still fits into memory range * if so we have a match. We do not assume that the memory ranges * are sorted by EFI, therefore we must record the match and only * keep the lowest possible one. */ if (addr < best_addr && addr < md_end && addr+size <= md_end) best_addr = addr; } if (best_addr == HIGHEST_ADDR) { free_memmap(&mdesc); ERR_PRT((L"Could not find memory suitable for loading image")); return -1; } *start = best_addr; free_memmap(&mdesc); return 0; }