/** * Transmit SRP SCSI command * * @v srp SRP device */ static void srp_cmd ( struct srp_device *srp ) { struct io_buffer *iobuf; struct srp_cmd *cmd; struct srp_memory_descriptor *data_out; struct srp_memory_descriptor *data_in; int rc; assert ( srp->state & SRP_STATE_LOGGED_IN ); /* Allocate I/O buffer */ iobuf = xfer_alloc_iob ( &srp->socket, SRP_MAX_I_T_IU_LEN ); if ( ! iobuf ) { rc = -ENOMEM; goto err; } /* Construct base portion */ cmd = iob_put ( iobuf, sizeof ( *cmd ) ); memset ( cmd, 0, sizeof ( *cmd ) ); cmd->type = SRP_CMD; cmd->tag.dwords[1] = htonl ( ++srp_tag ); cmd->lun = srp->lun; memcpy ( &cmd->cdb, &srp->command->cdb, sizeof ( cmd->cdb ) ); /* Construct data-out descriptor, if present */ if ( srp->command->data_out ) { cmd->data_buffer_formats |= SRP_CMD_DO_FMT_DIRECT; data_out = iob_put ( iobuf, sizeof ( *data_out ) ); data_out->address = cpu_to_be64 ( user_to_phys ( srp->command->data_out, 0 ) ); data_out->handle = ntohl ( srp->memory_handle ); data_out->len = ntohl ( srp->command->data_out_len ); } /* Construct data-in descriptor, if present */ if ( srp->command->data_in ) { cmd->data_buffer_formats |= SRP_CMD_DI_FMT_DIRECT; data_in = iob_put ( iobuf, sizeof ( *data_in ) ); data_in->address = cpu_to_be64 ( user_to_phys ( srp->command->data_in, 0 ) ); data_in->handle = ntohl ( srp->memory_handle ); data_in->len = ntohl ( srp->command->data_in_len ); } DBGC2 ( srp, "SRP %p TX SCSI command tag %08x%08x\n", srp, ntohl ( cmd->tag.dwords[0] ), ntohl ( cmd->tag.dwords[1] ) ); DBGC2_HDA ( srp, 0, iobuf->data, iob_len ( iobuf ) ); /* Send IU */ if ( ( rc = xfer_deliver_iob ( &srp->socket, iobuf ) ) != 0 ) { DBGC ( srp, "SRP %p could not send command: %s\n", srp, strerror ( rc ) ); goto err; } return; err: srp_fail ( srp, rc ); }
/** * 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; }
/** * 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; }
/** * Scan for SMBIOS entry point structure * * @v start Start address of region to scan * @v len Length of region to scan * @v entry SMBIOS entry point structure to fill in * @ret rc Return status code */ int find_smbios_entry ( userptr_t start, size_t len, struct smbios_entry *entry ) { uint8_t buf[256]; /* 256 is maximum length possible */ static size_t offset = 0; /* Avoid repeated attempts to locate SMBIOS */ size_t entry_len; unsigned int i; uint8_t sum; /* Try to find SMBIOS */ for ( ; offset < len ; offset += 0x10 ) { /* Read start of header and verify signature */ copy_from_user ( entry, start, offset, sizeof ( *entry ) ); if ( entry->signature != SMBIOS_SIGNATURE ) continue; /* Read whole header and verify checksum */ entry_len = entry->len; assert ( entry_len <= sizeof ( buf ) ); copy_from_user ( buf, start, offset, entry_len ); for ( i = 0, sum = 0 ; i < entry_len ; i++ ) { sum += buf[i]; } if ( sum != 0 ) { DBG ( "SMBIOS at %08lx has bad checksum %02x\n", user_to_phys ( start, offset ), sum ); continue; } /* Fill result structure */ DBG ( "Found SMBIOS v%d.%d entry point at %08lx\n", entry->major, entry->minor, user_to_phys ( start, offset ) ); return 0; } DBG ( "No SMBIOS found\n" ); return -ENODEV; }
/** * Prepare segment for loading * * @v segment Segment start * @v filesz Size of the "allocated bytes" portion of the segment * @v memsz Size of the segment * @ret rc Return status code */ int prep_segment ( userptr_t segment, size_t filesz, size_t memsz ) { struct memory_map memmap; physaddr_t start = user_to_phys ( segment, 0 ); physaddr_t mid = user_to_phys ( segment, filesz ); physaddr_t end = user_to_phys ( segment, memsz ); unsigned int i; DBG ( "Preparing segment [%lx,%lx,%lx)\n", start, mid, end ); /* Sanity check */ if ( filesz > memsz ) { DBG ( "Insane segment [%lx,%lx,%lx)\n", start, mid, end ); return -EINVAL; } /* Get a fresh memory map. This allows us to automatically * avoid treading on any regions that Etherboot is currently * editing out of the memory map. */ get_memmap ( &memmap ); /* Look for a suitable memory region */ for ( i = 0 ; i < memmap.count ; i++ ) { if ( ( start >= memmap.regions[i].start ) && ( end <= memmap.regions[i].end ) ) { /* Found valid region: zero bss and return */ memset_user ( segment, filesz, 0, ( memsz - filesz ) ); return 0; } } /* No suitable memory region found */ DBG ( "Segment [%lx,%lx,%lx) does not fit into available memory\n", start, mid, end ); return -ERANGE_SEGMENT; }
/** * Extract \_Sx value from DSDT/SSDT * * @v zsdt DSDT or SSDT * @v signature Signature (e.g. "_S5_") * @ret sx \_Sx value, or negative error * * In theory, extracting the \_Sx value from the DSDT/SSDT requires a * full ACPI parser plus some heuristics to work around the various * broken encodings encountered in real ACPI implementations. * * In practice, we can get the same result by scanning through the * DSDT/SSDT for the signature (e.g. "_S5_"), extracting the first * four bytes, removing any bytes with bit 3 set, and treating * whatever is left as a little-endian value. This is one of the * uglier hacks I have ever implemented, but it's still prettier than * the ACPI specification itself. */ static int acpi_sx_zsdt ( userptr_t zsdt, uint32_t signature ) { struct acpi_header acpi; union { uint32_t dword; uint8_t byte[4]; } buf; size_t offset; size_t len; unsigned int sx; uint8_t *byte; /* Read table header */ copy_from_user ( &acpi, zsdt, 0, sizeof ( acpi ) ); len = le32_to_cpu ( acpi.length ); /* Locate signature */ for ( offset = sizeof ( acpi ) ; ( ( offset + sizeof ( buf ) /* signature */ + 3 /* pkg header */ + sizeof ( buf ) /* value */ ) < len ) ; offset++ ) { /* Check signature */ copy_from_user ( &buf, zsdt, offset, sizeof ( buf ) ); if ( buf.dword != cpu_to_le32 ( signature ) ) continue; DBGC ( zsdt, "DSDT/SSDT %#08lx found %s at offset %#zx\n", user_to_phys ( zsdt, 0 ), acpi_name ( signature ), offset ); offset += sizeof ( buf ); /* Read first four bytes of value */ copy_from_user ( &buf, zsdt, ( offset + 3 /* pkg header */ ), sizeof ( buf ) ); DBGC ( zsdt, "DSDT/SSDT %#08lx found %s containing " "%02x:%02x:%02x:%02x\n", user_to_phys ( zsdt, 0 ), acpi_name ( signature ), buf.byte[0], buf.byte[1], buf.byte[2], buf.byte[3] ); /* Extract \Sx value. There are three potential * encodings that we might encounter: * * - SLP_TYPa, SLP_TYPb, rsvd, rsvd * * - <byteprefix>, SLP_TYPa, <byteprefix>, SLP_TYPb, ... * * - <dwordprefix>, SLP_TYPa, SLP_TYPb, 0, 0 * * Since <byteprefix> and <dwordprefix> both have bit * 3 set, and valid SLP_TYPx must have bit 3 clear * (since SLP_TYPx is a 3-bit field), we can just skip * any bytes with bit 3 set. */ byte = &buf.byte[0]; if ( *byte & 0x08 ) byte++; sx = *(byte++); if ( *byte & 0x08 ) byte++; sx |= ( *byte << 8 ); return sx; } return -ENOENT; }
/** * 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; }