| /* |
| * Firmware Interface Table support. |
| * |
| * Copyright (C) 2012 Google Inc. |
| * |
| * 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; version 2 of the License. |
| * |
| * 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 St, Fifth Floor, Boston, MA, 02110-1301 USA |
| */ |
| |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| #include "common.h" |
| #include "cbfs.h" |
| #include "cbfs_image.h" |
| #include "fit.h" |
| |
| /* FIXME: This code assumes it is being executed on a little endian machine. */ |
| |
| #define FIT_POINTER_LOCATION 0xffffffc0 |
| #define FIT_TABLE_LOWEST_ADDRESS ((uint32_t)(-(16 << 20))) |
| #define FIT_ENTRY_CHECKSUM_VALID 0x80 |
| #define FIT_TYPE_HEADER 0x0 |
| #define FIT_HEADER_VERSION 0x0100 |
| #define FIT_HEADER_ADDRESS "_FIT_ " |
| #define FIT_TYPE_MICROCODE 0x1 |
| #define FIT_MICROCODE_VERSION 0x0100 |
| |
| struct fit_entry { |
| uint64_t address; |
| uint32_t size_reserved; |
| uint16_t version; |
| uint8_t type_checksum_valid; |
| uint8_t checksum; |
| } __attribute__ ((packed)); |
| |
| struct fit_table { |
| struct fit_entry header; |
| struct fit_entry entries[0]; |
| } __attribute__ ((packed)); |
| |
| struct microcode_header { |
| uint32_t version; |
| uint32_t revision; |
| uint32_t date; |
| uint32_t processor_signature; |
| uint32_t checksum; |
| uint32_t loader_revision; |
| uint32_t processor_flags; |
| uint32_t data_size; |
| uint32_t total_size; |
| uint8_t reserved[12]; |
| } __attribute__ ((packed)); |
| |
| struct microcode_entry { |
| int offset; |
| int size; |
| }; |
| |
| static inline void *rom_buffer_pointer(struct cbfs_image *image, int offset) |
| { |
| return &image->buffer.data[offset]; |
| } |
| |
| static inline int fit_entry_size_bytes(struct fit_entry *entry) |
| { |
| return (entry->size_reserved & 0xffffff) << 4; |
| } |
| |
| static inline void fit_entry_update_size(struct fit_entry *entry, |
| int size_bytes) |
| { |
| /* Size is multiples of 16 bytes. */ |
| entry->size_reserved = (size_bytes >> 4) & 0xffffff; |
| } |
| |
| static inline void fit_entry_add_size(struct fit_entry *entry, |
| int size_bytes) |
| { |
| int size = fit_entry_size_bytes(entry); |
| size += size_bytes; |
| fit_entry_update_size(entry, size); |
| } |
| |
| static inline int fit_entry_type(struct fit_entry *entry) |
| { |
| return entry->type_checksum_valid & ~FIT_ENTRY_CHECKSUM_VALID; |
| } |
| |
| /* |
| * Get an offset from a host pointer. This function assumes the ROM is located |
| * in the host address space at [4G - romsize -> 4G). It also assume all |
| * pointers have values within this address range. |
| */ |
| static inline int ptr_to_offset(uint32_t romsize, uint32_t host_ptr) |
| { |
| return (int)(romsize + host_ptr); |
| } |
| |
| /* |
| * Get a pointer from an offset. This function assumes the ROM is located |
| * in the host address space at [4G - romsize -> 4G). It also assume all |
| * pointers have values within this address range. |
| */ |
| static inline uint32_t offset_to_ptr(uint32_t romsize, int offset) |
| { |
| return -(romsize - (uint32_t )offset); |
| } |
| |
| static struct fit_table *locate_fit_table(struct cbfs_image *image) |
| { |
| struct fit_table *table; |
| uint32_t *fit_pointer; |
| |
| fit_pointer = rom_buffer_pointer(image, |
| ptr_to_offset(image->buffer.size, FIT_POINTER_LOCATION)); |
| |
| /* Ensure pointer is below 4GiB and within 16MiB of 4GiB */ |
| if (fit_pointer[1] != 0 || fit_pointer[0] < FIT_TABLE_LOWEST_ADDRESS) |
| return NULL; |
| |
| table = rom_buffer_pointer(image, |
| ptr_to_offset(image->buffer.size, *fit_pointer)); |
| |
| /* Check that the address field has the proper signature. */ |
| if (strncmp((const char *)&table->header.address, FIT_HEADER_ADDRESS, |
| sizeof(table->header.address))) |
| return NULL; |
| |
| if (table->header.version != FIT_HEADER_VERSION) |
| return NULL; |
| |
| if (fit_entry_type(&table->header) != FIT_TYPE_HEADER) |
| return NULL; |
| |
| /* Assume that the FIT table only contains the header */ |
| if (fit_entry_size_bytes(&table->header) != sizeof(struct fit_entry)) |
| return NULL; |
| |
| return table; |
| } |
| |
| static void update_fit_checksum(struct fit_table *fit) |
| { |
| int size_bytes; |
| uint8_t *buffer; |
| uint8_t result; |
| int i; |
| |
| fit->header.checksum = 0; |
| size_bytes = fit_entry_size_bytes(&fit->header); |
| result = 0; |
| buffer = (void *)fit; |
| for (i = 0; i < size_bytes; i++) |
| result += buffer[i]; |
| fit->header.checksum = -result; |
| } |
| |
| static void add_microcodde_entries(struct cbfs_image *image, |
| struct fit_table *fit, |
| struct microcode_entry *mcus, int num_mcus) |
| { |
| int i; |
| |
| for (i = 0; i < num_mcus; i++) { |
| struct fit_entry *entry = &fit->entries[i]; |
| struct microcode_entry *mcu = &mcus[i]; |
| |
| entry->address = offset_to_ptr(image->buffer.size, mcu->offset); |
| fit_entry_update_size(entry, mcu->size); |
| entry->version = FIT_MICROCODE_VERSION; |
| entry->type_checksum_valid = FIT_TYPE_MICROCODE; |
| entry->checksum = 0; |
| fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); |
| } |
| } |
| |
| static int parse_microcode_blob(struct cbfs_image *image, |
| struct cbfs_file *mcode_file, |
| struct microcode_entry *mcus, int *total_mcus) |
| { |
| int num_mcus; |
| int current_offset; |
| int file_length; |
| |
| current_offset = (int)((char *)mcode_file - image->buffer.data); |
| current_offset += ntohl(mcode_file->offset); |
| file_length = ntohl(mcode_file->len); |
| |
| num_mcus = 0; |
| while (file_length > sizeof(struct microcode_header)) |
| { |
| struct microcode_header *mcu_header; |
| |
| mcu_header = rom_buffer_pointer(image, current_offset); |
| |
| /* Quickly sanity check a prospective microcode update. */ |
| if (mcu_header->total_size < sizeof(*mcu_header)) |
| break; |
| |
| /* FIXME: Should the checksum be validated? */ |
| mcus[num_mcus].offset = current_offset; |
| mcus[num_mcus].size = mcu_header->total_size; |
| |
| /* Proceed to next payload. */ |
| current_offset += mcus[num_mcus].size; |
| num_mcus++; |
| file_length -= mcus[num_mcus].size; |
| |
| /* Reached limit of FIT entries. */ |
| if (num_mcus == *total_mcus) |
| break; |
| if (file_length < sizeof(struct microcode_header)) |
| break; |
| } |
| |
| /* Update how many microcode updates we found. */ |
| *total_mcus = num_mcus; |
| |
| return 0; |
| } |
| |
| int fit_update_table(struct cbfs_image *image, int empty_entries, |
| const char *microcode_blob_name) |
| { |
| struct fit_table *fit; |
| struct cbfs_file *mcode_file; |
| struct microcode_entry *mcus; |
| int ret = 0; |
| // struct rom_image image = { .rom = rom, .size = romsize, }; |
| |
| fit = locate_fit_table(image); |
| |
| if (!fit) { |
| ERROR("FIT not found.\n"); |
| return 1; |
| } |
| |
| mcode_file = cbfs_get_entry(image, microcode_blob_name); |
| if (!mcode_file) { |
| ERROR("File '%s' not found in CBFS.\n", |
| microcode_blob_name); |
| return 1; |
| } |
| |
| mcus = malloc(sizeof(*mcus) * empty_entries); |
| |
| if (!mcus) { |
| ERROR("Couldn't allocate memory for microcode update entries.\n"); |
| return 1; |
| } |
| |
| if (parse_microcode_blob(image, mcode_file, mcus, &empty_entries)) { |
| ERROR("Couldn't parse microcode blob.\n"); |
| ret = 1; |
| goto out; |
| } |
| |
| add_microcodde_entries(image, fit, mcus, empty_entries); |
| update_fit_checksum(fit); |
| |
| out: |
| free(mcus); |
| return ret; |
| } |