/** * Perform a series of memory copies from a list in low memory */ static void shuffle ( unsigned int list_segment, unsigned int list_offset, unsigned int count ) { comboot_shuffle_descriptor shuf[COMBOOT_MAX_SHUFFLE_DESCRIPTORS]; unsigned int i; /* Copy shuffle descriptor list so it doesn't get overwritten */ copy_from_user ( shuf, real_to_user ( list_segment, list_offset ), 0, count * sizeof( comboot_shuffle_descriptor ) ); /* Do the copies */ for ( i = 0; i < count; i++ ) { userptr_t src_u = phys_to_user ( shuf[ i ].src ); userptr_t dest_u = phys_to_user ( shuf[ i ].dest ); if ( shuf[ i ].src == 0xFFFFFFFF ) { /* Fill with 0 instead of copying */ memset_user ( dest_u, 0, 0, shuf[ i ].len ); } else if ( shuf[ i ].dest == 0xFFFFFFFF ) { /* Copy new list of descriptors */ count = shuf[ i ].len / sizeof( comboot_shuffle_descriptor ); assert ( count <= COMBOOT_MAX_SHUFFLE_DESCRIPTORS ); copy_from_user ( shuf, src_u, 0, shuf[ i ].len ); i = -1; } else { /* Regular copy */ memmove_user ( dest_u, 0, src_u, 0, shuf[ i ].len ); } } }
/** * Locate ACPI root system description table within a memory range * * @v start Start address to search * @v len Length to search * @ret rsdt ACPI root system description table, or UNULL */ static userptr_t acpi_find_rsdt_range ( userptr_t start, size_t len ) { static const char signature[8] = RSDP_SIGNATURE; struct acpi_rsdp rsdp; userptr_t rsdt; size_t offset; uint8_t sum; unsigned int i; /* Search for RSDP */ for ( offset = 0 ; ( ( offset + sizeof ( rsdp ) ) < len ) ; offset += RSDP_STRIDE ) { /* Check signature and checksum */ copy_from_user ( &rsdp, start, offset, sizeof ( rsdp ) ); if ( memcmp ( rsdp.signature, signature, sizeof ( signature ) ) != 0 ) continue; for ( sum = 0, i = 0 ; i < sizeof ( rsdp ) ; i++ ) sum += *( ( ( uint8_t * ) &rsdp ) + i ); if ( sum != 0 ) continue; /* Extract RSDT */ rsdt = phys_to_user ( le32_to_cpu ( rsdp.rsdt ) ); DBGC ( rsdt, "RSDT %#08lx found via RSDP %#08lx\n", user_to_phys ( rsdt, 0 ), user_to_phys ( start, offset ) ); return rsdt; } return UNULL; }
/** * Extract \_Sx value from DSDT/SSDT * * @v rsdt ACPI root system description table * @v signature Signature (e.g. "_S5_") * @ret sx \_Sx value, or negative error */ int acpi_sx ( userptr_t rsdt, uint32_t signature ) { struct acpi_fadt fadtab; userptr_t fadt; userptr_t dsdt; userptr_t ssdt; unsigned int i; int sx; /* Try DSDT first */ fadt = acpi_find ( rsdt, FADT_SIGNATURE, 0 ); if ( fadt ) { copy_from_user ( &fadtab, fadt, 0, sizeof ( fadtab ) ); dsdt = phys_to_user ( fadtab.dsdt ); if ( ( sx = acpi_sx_zsdt ( dsdt, signature ) ) >= 0 ) return sx; } /* Try all SSDTs */ for ( i = 0 ; ; i++ ) { ssdt = acpi_find ( rsdt, SSDT_SIGNATURE, i ); if ( ! ssdt ) break; if ( ( sx = acpi_sx_zsdt ( ssdt, signature ) ) >= 0 ) return sx; } DBGC ( rsdt, "RSDT %#08lx could not find \\_Sx \"%s\"\n", user_to_phys ( rsdt, 0 ), acpi_name ( signature ) ); return -ENOENT; }
/** * Initialise initrd * * @ret rc Return status code */ static int initrd_init ( void ) { struct image *image; int rc; /* Do nothing if no initrd was specified */ if ( ! initrd_phys ) { DBGC ( colour, "RUNTIME found no initrd\n" ); return 0; } if ( ! initrd_len ) { DBGC ( colour, "RUNTIME found empty initrd\n" ); return 0; } DBGC ( colour, "RUNTIME found initrd at [%x,%x)\n", initrd_phys, ( initrd_phys + initrd_len ) ); /* Allocate image */ image = alloc_image(); if ( ! image ) { DBGC ( colour, "RUNTIME could not allocate image for " "initrd\n" ); rc = -ENOMEM; goto err_alloc_image; } image_set_name ( image, "<INITRD>" ); /* Allocate and copy initrd content */ image->data = umalloc ( initrd_len ); if ( ! image->data ) { DBGC ( colour, "RUNTIME could not allocate %zd bytes for " "initrd\n", initrd_len ); rc = -ENOMEM; goto err_umalloc; } image->len = initrd_len; memcpy_user ( image->data, 0, phys_to_user ( initrd_phys ), 0, initrd_len ); /* Mark initrd as consumed */ initrd_phys = 0; /* Register image */ if ( ( rc = register_image ( image ) ) != 0 ) { DBGC ( colour, "RUNTIME could not register initrd: %s\n", strerror ( rc ) ); goto err_register_image; } /* Drop our reference to the image */ image_put ( image ); return 0; err_register_image: err_umalloc: image_put ( image ); err_alloc_image: return rc; }
/** * Locate ACPI root system description table * * @ret rsdt ACPI root system description table, or UNULL */ static userptr_t efi_find_rsdt ( void ) { /* Locate RSDT via ACPI configuration table, if available */ if ( rsdp ) return phys_to_user ( rsdp->RsdtAddress ); return UNULL; }
/** * Initialise command line * * @ret rc Return status code */ static int cmdline_init ( void ) { userptr_t cmdline_user; char *cmdline; size_t len; int rc; /* Do nothing if no command line was specified */ if ( ! cmdline_phys ) { DBGC ( colour, "RUNTIME found no command line\n" ); return 0; } cmdline_user = phys_to_user ( cmdline_phys ); len = ( strlen_user ( cmdline_user, 0 ) + 1 /* NUL */ ); /* Allocate and copy command line */ cmdline_copy = malloc ( len ); if ( ! cmdline_copy ) { DBGC ( colour, "RUNTIME could not allocate %zd bytes for " "command line\n", len ); rc = -ENOMEM; goto err_alloc_cmdline_copy; } cmdline = cmdline_copy; copy_from_user ( cmdline, cmdline_user, 0, len ); DBGC ( colour, "RUNTIME found command line \"%s\" at %08x\n", cmdline, cmdline_phys ); /* Mark command line as consumed */ cmdline_phys = 0; /* Strip unwanted cruft from the command line */ cmdline_strip ( cmdline, "BOOT_IMAGE=" ); cmdline_strip ( cmdline, "initrd=" ); while ( isspace ( *cmdline ) ) cmdline++; DBGC ( colour, "RUNTIME using command line \"%s\"\n", cmdline ); /* Prepare and register image */ cmdline_image.data = virt_to_user ( cmdline ); cmdline_image.len = strlen ( cmdline ); if ( cmdline_image.len ) { if ( ( rc = register_image ( &cmdline_image ) ) != 0 ) { DBGC ( colour, "RUNTIME could not register command " "line: %s\n", strerror ( rc ) ); goto err_register_image; } } /* Drop our reference to the image */ image_put ( &cmdline_image ); return 0; err_register_image: image_put ( &cmdline_image ); err_alloc_cmdline_copy: return rc; }
/** * Reallocate external memory * * @v old_ptr Memory previously allocated by umalloc(), or UNULL * @v new_size Requested size * @ret new_ptr Allocated memory, or UNULL * * Calling realloc() with a new size of zero is a valid way to free a * memory block. */ static userptr_t efi_urealloc ( userptr_t old_ptr, size_t new_size ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_PHYSICAL_ADDRESS phys_addr; unsigned int new_pages, old_pages; userptr_t new_ptr = UNOWHERE; size_t old_size; EFI_STATUS efirc; int rc; /* Allocate new memory if necessary. If allocation fails, * return without touching the old block. */ if ( new_size ) { new_pages = ( EFI_SIZE_TO_PAGES ( new_size ) + 1 ); if ( ( efirc = bs->AllocatePages ( AllocateAnyPages, EfiBootServicesData, new_pages, &phys_addr ) ) != 0 ) { rc = -EEFI ( efirc ); DBG ( "EFI could not allocate %d pages: %s\n", new_pages, strerror ( rc ) ); return UNULL; } assert ( phys_addr != 0 ); new_ptr = phys_to_user ( phys_addr + EFI_PAGE_SIZE ); copy_to_user ( new_ptr, -EFI_PAGE_SIZE, &new_size, sizeof ( new_size ) ); DBG ( "EFI allocated %d pages at %llx\n", new_pages, phys_addr ); } /* Copy across relevant part of the old data region (if any), * then free it. Note that at this point either (a) new_ptr * is valid, or (b) new_size is 0; either way, the memcpy() is * valid. */ if ( old_ptr && ( old_ptr != UNOWHERE ) ) { copy_from_user ( &old_size, old_ptr, -EFI_PAGE_SIZE, sizeof ( old_size ) ); memcpy_user ( new_ptr, 0, old_ptr, 0, ( (old_size < new_size) ? old_size : new_size )); old_pages = ( EFI_SIZE_TO_PAGES ( old_size ) + 1 ); phys_addr = user_to_phys ( old_ptr, -EFI_PAGE_SIZE ); if ( ( efirc = bs->FreePages ( phys_addr, old_pages ) ) != 0 ){ rc = -EEFI ( efirc ); DBG ( "EFI could not free %d pages at %llx: %s\n", old_pages, phys_addr, strerror ( rc ) ); /* Not fatal; we have leaked memory but successfully * allocated (if asked to do so). */ } DBG ( "EFI freed %d pages at %llx\n", old_pages, phys_addr ); } return new_ptr; }
/** * Cached DHCPACK startup function * */ static void cachedhcp_init ( void ) { struct dhcp_packet *dhcppkt; struct dhcp_packet *tmp; struct dhcphdr *dhcphdr; size_t len; /* Do nothing if no cached DHCPACK is present */ if ( ! cached_dhcpack_phys ) { DBGC ( colour, "CACHEDHCP found no cached DHCPACK\n" ); return; } /* No reliable way to determine length before parsing packet; * start by assuming maximum length permitted by PXE. */ len = sizeof ( BOOTPLAYER_t ); /* Allocate and populate DHCP packet */ dhcppkt = zalloc ( sizeof ( *dhcppkt ) + len ); if ( ! dhcppkt ) { DBGC ( colour, "CACHEDHCP could not allocate copy\n" ); return; } dhcphdr = ( ( ( void * ) dhcppkt ) + sizeof ( *dhcppkt ) ); copy_from_user ( dhcphdr, phys_to_user ( cached_dhcpack_phys ), 0, len ); dhcppkt_init ( dhcppkt, dhcphdr, len ); /* Resize packet to required length. If reallocation fails, * just continue to use the original packet. */ len = dhcppkt_len ( dhcppkt ); tmp = realloc ( dhcppkt, ( sizeof ( *dhcppkt ) + len ) ); if ( tmp ) dhcppkt = tmp; /* Reinitialise packet at new address */ dhcphdr = ( ( ( void * ) dhcppkt ) + sizeof ( *dhcppkt ) ); dhcppkt_init ( dhcppkt, dhcphdr, len ); /* Store as cached DHCPACK, and mark original copy as consumed */ DBGC ( colour, "CACHEDHCP found cached DHCPACK at %08x+%zx\n", cached_dhcpack_phys, len ); cached_dhcpack = dhcppkt; cached_dhcpack_phys = 0; }
/** * Load ELF segment into memory * * @v image ELF file * @v phdr ELF program header * @ret rc Return status code */ static int elf_load_segment ( struct image *image, Elf_Phdr *phdr ) { physaddr_t dest; userptr_t buffer; int rc; /* Do nothing for non-PT_LOAD segments */ if ( phdr->p_type != PT_LOAD ) return 0; /* Check segment lies within image */ if ( ( phdr->p_offset + phdr->p_filesz ) > image->len ) { DBG ( "ELF segment outside ELF file\n" ); return -ENOEXEC; } /* Find start address: use physical address for preference, * fall back to virtual address if no physical address * supplied. */ dest = phdr->p_paddr; if ( ! dest ) dest = phdr->p_vaddr; if ( ! dest ) { DBG ( "ELF segment loads to physical address 0\n" ); return -ENOEXEC; } buffer = phys_to_user ( dest ); DBG ( "ELF loading segment [%x,%x) to [%x,%x,%x)\n", phdr->p_offset, ( phdr->p_offset + phdr->p_filesz ), phdr->p_paddr, ( phdr->p_paddr + phdr->p_filesz ), ( phdr->p_paddr + phdr->p_memsz ) ); /* Verify and prepare segment */ if ( ( rc = prep_segment ( buffer, phdr->p_filesz, phdr->p_memsz ) ) != 0 ) { DBG ( "ELF could not prepare segment: %s\n", strerror ( rc ) ); return rc; } /* Copy image to segment */ memcpy_user ( buffer, 0, image->data, phdr->p_offset, phdr->p_filesz ); return 0; }
/** * Locate ACPI root system description table * * @v ebda Extended BIOS data area, or UNULL * @ret rsdt ACPI root system description table, or UNULL */ userptr_t acpi_find_rsdt ( userptr_t ebda ) { userptr_t rsdt; /* Search EBDA, if applicable */ if ( ebda ) { rsdt = acpi_find_rsdt_range ( ebda, RSDP_EBDA_LEN ); if ( rsdt ) return rsdt; } /* Search fixed BIOS area */ rsdt = acpi_find_rsdt_range ( phys_to_user ( RSDP_BIOS_START ), RSDP_BIOS_LEN ); if ( rsdt ) return rsdt; return UNULL; }
/** * Find SMBIOS * * @v smbios SMBIOS entry point descriptor structure to fill in * @ret rc Return status code */ static int efi_find_smbios ( struct smbios *smbios ) { if ( ! smbios_entry ) { DBG ( "No SMBIOS table provided\n" ); return -ENODEV; } if ( smbios_entry->signature != SMBIOS_SIGNATURE ) { DBG ( "Invalid SMBIOS signature\n" ); return -ENODEV; } smbios->address = phys_to_user ( smbios_entry->smbios_address ); smbios->len = smbios_entry->smbios_len; smbios->count = smbios_entry->smbios_count; DBG ( "Found SMBIOS v%d.%d entry point at %p (%x+%zx)\n", smbios_entry->major, smbios_entry->minor, smbios_entry, smbios_entry->smbios_address, smbios->len ); return 0; }
/** * Load ELF segment into memory * * @v image ELF file * @v phdr ELF program header * @v ehdr ELF executable header * @ret entry Entry point, if found * @ret max Maximum used address * @ret rc Return status code */ static int elf_load_segment ( struct image *image, Elf_Phdr *phdr, Elf_Ehdr *ehdr, physaddr_t *entry, physaddr_t *max ) { physaddr_t dest; physaddr_t end; userptr_t buffer; unsigned long e_offset; int rc; /* Do nothing for non-PT_LOAD segments */ if ( phdr->p_type != PT_LOAD ) return 0; /* Check segment lies within image */ if ( ( phdr->p_offset + phdr->p_filesz ) > image->len ) { DBGC ( image, "ELF %p segment outside image\n", image ); return -ENOEXEC; } /* Find start address: use physical address for preference, * fall back to virtual address if no physical address * supplied. */ dest = phdr->p_paddr; if ( ! dest ) dest = phdr->p_vaddr; if ( ! dest ) { DBGC ( image, "ELF %p segment loads to physical address 0\n", image ); return -ENOEXEC; } buffer = phys_to_user ( dest ); end = ( dest + phdr->p_memsz ); DBGC ( image, "ELF %p loading segment [%x,%x) to [%x,%x,%x)\n", image, phdr->p_offset, ( phdr->p_offset + phdr->p_filesz ), phdr->p_paddr, ( phdr->p_paddr + phdr->p_filesz ), ( phdr->p_paddr + phdr->p_memsz ) ); /* Verify and prepare segment */ if ( ( rc = prep_segment ( buffer, phdr->p_filesz, phdr->p_memsz ) ) != 0 ) { DBGC ( image, "ELF %p could not prepare segment: %s\n", image, strerror ( rc ) ); return rc; } /* Update maximum used address, if applicable */ if ( end > *max ) *max = end; /* Copy image to segment */ memcpy_user ( buffer, 0, image->data, phdr->p_offset, phdr->p_filesz ); /* Set execution address, if it lies within this segment */ if ( ( e_offset = ( ehdr->e_entry - dest ) ) < phdr->p_filesz ) { *entry = ehdr->e_entry; DBGC ( image, "ELF %p found physical entry point at %lx\n", image, *entry ); } else if ( ( e_offset = ( ehdr->e_entry - phdr->p_vaddr ) ) < phdr->p_filesz ) { if ( ! *entry ) { *entry = ( dest + e_offset ); DBGC ( image, "ELF %p found virtual entry point at %lx" " (virt %lx)\n", image, *entry, ( ( unsigned long ) ehdr->e_entry ) ); } } return 0; }
/** * Locate ACPI table * * @v rsdt ACPI root system description table * @v signature Requested table signature * @v index Requested index of table with this signature * @ret table Table, or UNULL if not found */ userptr_t acpi_find ( userptr_t rsdt, uint32_t signature, unsigned int index ) { struct acpi_header acpi; struct acpi_rsdt *rsdtab; typeof ( rsdtab->entry[0] ) entry; userptr_t table; size_t len; unsigned int count; unsigned int i; /* Read RSDT header */ copy_from_user ( &acpi, rsdt, 0, sizeof ( acpi ) ); if ( acpi.signature != cpu_to_le32 ( RSDT_SIGNATURE ) ) { DBGC ( rsdt, "RSDT %#08lx has invalid signature:\n", user_to_phys ( rsdt, 0 ) ); DBGC_HDA ( rsdt, user_to_phys ( rsdt, 0 ), &acpi, sizeof ( acpi ) ); return UNULL; } len = le32_to_cpu ( acpi.length ); if ( len < sizeof ( rsdtab->acpi ) ) { DBGC ( rsdt, "RSDT %#08lx has invalid length:\n", user_to_phys ( rsdt, 0 ) ); DBGC_HDA ( rsdt, user_to_phys ( rsdt, 0 ), &acpi, sizeof ( acpi ) ); return UNULL; } /* Calculate number of entries */ count = ( ( len - sizeof ( rsdtab->acpi ) ) / sizeof ( entry ) ); /* Search through entries */ for ( i = 0 ; i < count ; i++ ) { /* Get table address */ copy_from_user ( &entry, rsdt, offsetof ( typeof ( *rsdtab ), entry[i] ), sizeof ( entry ) ); /* Read table header */ table = phys_to_user ( entry ); copy_from_user ( &acpi.signature, table, 0, sizeof ( acpi.signature ) ); /* Check table signature */ if ( acpi.signature != cpu_to_le32 ( signature ) ) continue; /* Check index */ if ( index-- ) continue; DBGC ( rsdt, "RSDT %#08lx found %s at %08lx\n", user_to_phys ( rsdt, 0 ), acpi_name ( signature ), user_to_phys ( table, 0 ) ); return table; } DBGC ( rsdt, "RSDT %#08lx could not find %s\n", user_to_phys ( rsdt, 0 ), acpi_name ( signature ) ); return UNULL; }