Patrick Georgi | ea063cb | 2020-05-08 19:28:13 +0200 | [diff] [blame] | 1 | /* Firmware Interface Table support */ |
Patrick Georgi | 7333a11 | 2020-05-08 20:48:04 +0200 | [diff] [blame] | 2 | /* SPDX-License-Identifier: GPL-2.0-only */ |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 3 | |
Joel Kitching | b4a1981 | 2019-06-25 17:28:30 +0800 | [diff] [blame] | 4 | #include <inttypes.h> |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 5 | #include <stdint.h> |
Sol Boucher | 32532ac | 2015-05-06 14:44:40 -0700 | [diff] [blame] | 6 | #include <stdio.h> |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 7 | #include <stdlib.h> |
| 8 | #include <string.h> |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 9 | |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 10 | #include "fit.h" |
| 11 | |
| 12 | /* FIXME: This code assumes it is being executed on a little endian machine. */ |
| 13 | |
| 14 | #define FIT_POINTER_LOCATION 0xffffffc0 |
| 15 | #define FIT_TABLE_LOWEST_ADDRESS ((uint32_t)(-(16 << 20))) |
| 16 | #define FIT_ENTRY_CHECKSUM_VALID 0x80 |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 17 | #define FIT_HEADER_VERSION 0x0100 |
| 18 | #define FIT_HEADER_ADDRESS "_FIT_ " |
| 19 | #define FIT_MICROCODE_VERSION 0x0100 |
| 20 | #define FIT_TXT_VERSION 0x0100 |
| 21 | |
| 22 | #define FIT_SIZE_ALIGNMENT 16 |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 23 | |
| 24 | struct fit_entry { |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 25 | /** |
| 26 | * Address is the base address of the firmware component |
| 27 | * must be aligned on 16 byte boundary |
| 28 | */ |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 29 | uint64_t address; |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 30 | /** |
| 31 | * Size is the span of the component in multiple of 16 bytes |
| 32 | * Bits [24:31] are reserved and must be set to 0 |
| 33 | */ |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 34 | uint32_t size_reserved; |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 35 | /** |
| 36 | * Component's version number in binary coded decimal (BCD) format. |
| 37 | * For the FIT header entry, the value in this field will indicate the |
| 38 | * revision number of the FIT data structure. The upper byte of the |
| 39 | * revision field indicates the major revision and the lower byte |
| 40 | * indicates the minor revision. |
| 41 | */ |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 42 | uint16_t version; |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 43 | /** |
| 44 | * FIT types 0x00 to 0x7F |
| 45 | * Bit 7 (C_V) indicates whether component has valid checksum. |
| 46 | */ |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 47 | uint8_t type_checksum_valid; |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 48 | /** |
| 49 | * Component's checksum. The modulo sum of all the bytes in the |
| 50 | * component and the value in this field (Chksum) must add up to zero. |
| 51 | * This field is only valid if the C_V flag is non-zero. |
| 52 | */ |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 53 | uint8_t checksum; |
Stefan Reinauer | 6a00113 | 2017-07-13 02:20:27 +0200 | [diff] [blame] | 54 | } __packed; |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 55 | |
| 56 | struct fit_table { |
| 57 | struct fit_entry header; |
Sol Boucher | 0e53931 | 2015-03-05 15:38:03 -0800 | [diff] [blame] | 58 | struct fit_entry entries[]; |
Stefan Reinauer | 6a00113 | 2017-07-13 02:20:27 +0200 | [diff] [blame] | 59 | } __packed; |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 60 | |
| 61 | struct microcode_header { |
| 62 | uint32_t version; |
| 63 | uint32_t revision; |
| 64 | uint32_t date; |
| 65 | uint32_t processor_signature; |
| 66 | uint32_t checksum; |
| 67 | uint32_t loader_revision; |
| 68 | uint32_t processor_flags; |
| 69 | uint32_t data_size; |
| 70 | uint32_t total_size; |
| 71 | uint8_t reserved[12]; |
Stefan Reinauer | 6a00113 | 2017-07-13 02:20:27 +0200 | [diff] [blame] | 72 | } __packed; |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 73 | |
| 74 | struct microcode_entry { |
| 75 | int offset; |
| 76 | int size; |
| 77 | }; |
| 78 | |
Sol Boucher | 32532ac | 2015-05-06 14:44:40 -0700 | [diff] [blame] | 79 | static inline void *rom_buffer_pointer(struct buffer *buffer, int offset) |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 80 | { |
Sol Boucher | 32532ac | 2015-05-06 14:44:40 -0700 | [diff] [blame] | 81 | return &buffer->data[offset]; |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 82 | } |
| 83 | |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 84 | static inline size_t fit_entry_size_bytes(const struct fit_entry *entry) |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 85 | { |
| 86 | return (entry->size_reserved & 0xffffff) << 4; |
| 87 | } |
| 88 | |
| 89 | static inline void fit_entry_update_size(struct fit_entry *entry, |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 90 | const int size_bytes) |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 91 | { |
| 92 | /* Size is multiples of 16 bytes. */ |
| 93 | entry->size_reserved = (size_bytes >> 4) & 0xffffff; |
| 94 | } |
| 95 | |
| 96 | static inline void fit_entry_add_size(struct fit_entry *entry, |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 97 | const int size_bytes) |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 98 | { |
| 99 | int size = fit_entry_size_bytes(entry); |
| 100 | size += size_bytes; |
| 101 | fit_entry_update_size(entry, size); |
| 102 | } |
| 103 | |
| 104 | static inline int fit_entry_type(struct fit_entry *entry) |
| 105 | { |
| 106 | return entry->type_checksum_valid & ~FIT_ENTRY_CHECKSUM_VALID; |
| 107 | } |
| 108 | |
| 109 | /* |
| 110 | * Get an offset from a host pointer. This function assumes the ROM is located |
| 111 | * in the host address space at [4G - romsize -> 4G). It also assume all |
| 112 | * pointers have values within this address range. |
| 113 | */ |
Sol Boucher | 32532ac | 2015-05-06 14:44:40 -0700 | [diff] [blame] | 114 | static inline int ptr_to_offset(fit_offset_converter_t helper, |
| 115 | const struct buffer *region, uint32_t host_ptr) |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 116 | { |
Sol Boucher | 32532ac | 2015-05-06 14:44:40 -0700 | [diff] [blame] | 117 | return helper(region, -host_ptr); |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 118 | } |
| 119 | |
| 120 | /* |
| 121 | * Get a pointer from an offset. This function assumes the ROM is located |
| 122 | * in the host address space at [4G - romsize -> 4G). It also assume all |
| 123 | * pointers have values within this address range. |
| 124 | */ |
Sol Boucher | 32532ac | 2015-05-06 14:44:40 -0700 | [diff] [blame] | 125 | static inline uint32_t offset_to_ptr(fit_offset_converter_t helper, |
| 126 | const struct buffer *region, int offset) |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 127 | { |
Sol Boucher | 32532ac | 2015-05-06 14:44:40 -0700 | [diff] [blame] | 128 | return -helper(region, offset); |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 129 | } |
| 130 | |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 131 | /* |
| 132 | * Return the number of FIT entries. |
| 133 | */ |
| 134 | static inline size_t fit_table_entries(const struct fit_table *fit) |
| 135 | { |
| 136 | if (!fit) |
| 137 | return 0; |
| 138 | |
| 139 | return (fit_entry_size_bytes(&fit->header) / FIT_SIZE_ALIGNMENT) - 1; |
| 140 | } |
| 141 | |
| 142 | /* |
| 143 | * Return the number of unused entries. |
| 144 | */ |
| 145 | static inline size_t fit_free_space(struct fit_table *fit, |
| 146 | const size_t max_entries) |
| 147 | { |
| 148 | if (!fit) |
| 149 | return 0; |
| 150 | |
| 151 | return max_entries - fit_table_entries(fit); |
| 152 | } |
| 153 | |
| 154 | /* |
| 155 | * Sort entries by type and fill gaps (entries with type unused). |
| 156 | * To be called after adding or deleting entries. |
| 157 | * |
| 158 | * This one is critical, as mentioned in Chapter 1.2.1 "FIT Ordering Rules" |
| 159 | * "Firmware Interface Table BIOS Specification". |
| 160 | * |
Patrick Georgi | 01cfecc | 2020-01-29 13:31:16 +0100 | [diff] [blame] | 161 | * We need to use a stable sorting algorithm, as the order of |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 162 | * FIT_TYPE_BIOS_STARTUP matter for measurements. |
| 163 | */ |
| 164 | static void sort_fit_table(struct fit_table *fit) |
| 165 | { |
| 166 | struct fit_entry tmp; |
| 167 | size_t i, j; |
| 168 | int swapped; |
| 169 | |
| 170 | /* Bubble sort entries */ |
| 171 | for (j = 0; j < fit_table_entries(fit) - 1; j++) { |
| 172 | swapped = 0; |
| 173 | for (i = 0; i < fit_table_entries(fit) - j - 1; i++) { |
| 174 | if (fit->entries[i].type_checksum_valid <= |
| 175 | fit->entries[i + 1].type_checksum_valid) |
| 176 | continue; |
| 177 | /* SWAP entries */ |
| 178 | memcpy(&tmp, &fit->entries[i], sizeof(tmp)); |
| 179 | memcpy(&fit->entries[i], &fit->entries[i + 1], |
| 180 | sizeof(fit->entries[i])); |
| 181 | memcpy(&fit->entries[i + 1], &tmp, |
| 182 | sizeof(fit->entries[i + 1])); |
| 183 | swapped = 1; |
| 184 | } |
| 185 | if (!swapped) |
| 186 | break; |
| 187 | } |
| 188 | } |
| 189 | |
Rizwan Qureshi | c1072f2 | 2018-06-04 23:02:46 +0530 | [diff] [blame] | 190 | static int fit_table_verified(struct fit_table *table) |
| 191 | { |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 192 | if (!table) |
| 193 | return 0; |
| 194 | |
Rizwan Qureshi | c1072f2 | 2018-06-04 23:02:46 +0530 | [diff] [blame] | 195 | /* Check that the address field has the proper signature. */ |
| 196 | if (strncmp((const char *)&table->header.address, FIT_HEADER_ADDRESS, |
| 197 | sizeof(table->header.address))) |
| 198 | return 0; |
| 199 | |
| 200 | if (table->header.version != FIT_HEADER_VERSION) |
| 201 | return 0; |
| 202 | |
| 203 | if (fit_entry_type(&table->header) != FIT_TYPE_HEADER) |
| 204 | return 0; |
| 205 | |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 206 | /* Assume that the FIT table contains at least the header */ |
| 207 | if (fit_entry_size_bytes(&table->header) < sizeof(struct fit_entry)) |
Rizwan Qureshi | c1072f2 | 2018-06-04 23:02:46 +0530 | [diff] [blame] | 208 | return 0; |
| 209 | |
| 210 | return 1; |
| 211 | } |
| 212 | |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 213 | /* |
| 214 | * Update the FIT checksum. |
| 215 | * To be called after modifiying the table. |
| 216 | */ |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 217 | static void update_fit_checksum(struct fit_table *fit) |
| 218 | { |
| 219 | int size_bytes; |
| 220 | uint8_t *buffer; |
| 221 | uint8_t result; |
| 222 | int i; |
| 223 | |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 224 | if (!fit) |
| 225 | return; |
| 226 | |
Aaron Durbin | 6b0d0d6 | 2012-12-14 17:16:21 -0600 | [diff] [blame] | 227 | fit->header.checksum = 0; |
| 228 | size_bytes = fit_entry_size_bytes(&fit->header); |
| 229 | result = 0; |
| 230 | buffer = (void *)fit; |
| 231 | for (i = 0; i < size_bytes; i++) |
| 232 | result += buffer[i]; |
| 233 | fit->header.checksum = -result; |
| 234 | } |
| 235 | |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 236 | /* |
| 237 | * Return a pointer to the next free entry. |
| 238 | * Caller must take care if enough space is available. |
| 239 | */ |
| 240 | static struct fit_entry *get_next_free_entry(struct fit_table *fit) |
| 241 | { |
| 242 | return &fit->entries[fit_table_entries(fit)]; |
| 243 | } |
| 244 | |
| 245 | static void fit_location_from_cbfs_header(uint32_t *current_offset, |
| 246 | uint32_t *file_length, void *ptr) |
| 247 | { |
| 248 | struct buffer buf; |
| 249 | struct cbfs_file header; |
| 250 | memset(&buf, 0, sizeof(buf)); |
| 251 | |
| 252 | buf.data = ptr; |
| 253 | buf.size = sizeof(header); |
| 254 | |
| 255 | bgets(&buf, header.magic, sizeof(header.magic)); |
| 256 | header.len = xdr_be.get32(&buf); |
| 257 | header.type = xdr_be.get32(&buf); |
| 258 | header.attributes_offset = xdr_be.get32(&buf); |
| 259 | header.offset = xdr_be.get32(&buf); |
| 260 | |
| 261 | *current_offset = header.offset; |
| 262 | *file_length = header.len; |
| 263 | } |
| 264 | |
| 265 | static int |
| 266 | parse_microcode_blob(struct cbfs_image *image, |
| 267 | const char *blob_name, |
| 268 | size_t *mcus_found, |
| 269 | struct microcode_entry *mcus, |
| 270 | const size_t max_fit_entries) |
| 271 | { |
| 272 | size_t num_mcus; |
| 273 | uint32_t current_offset; |
| 274 | uint32_t file_length; |
| 275 | struct cbfs_file *mcode_file; |
| 276 | |
| 277 | mcode_file = cbfs_get_entry(image, blob_name); |
Jeremy Compostella | e1465e2 | 2022-09-08 10:11:53 -0700 | [diff] [blame] | 278 | if (!mcode_file) { |
| 279 | ERROR("Couldn't find microcode blob.\n"); |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 280 | return 1; |
Jeremy Compostella | e1465e2 | 2022-09-08 10:11:53 -0700 | [diff] [blame] | 281 | } |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 282 | |
| 283 | fit_location_from_cbfs_header(¤t_offset, &file_length, |
| 284 | mcode_file); |
| 285 | current_offset += cbfs_get_entry_addr(image, mcode_file); |
| 286 | |
| 287 | num_mcus = 0; |
| 288 | while (file_length > sizeof(struct microcode_header)) { |
| 289 | const struct microcode_header *mcu_header; |
| 290 | |
| 291 | mcu_header = rom_buffer_pointer(&image->buffer, current_offset); |
| 292 | if (!mcu_header) { |
| 293 | ERROR("Couldn't parse microcode header.\n"); |
| 294 | return 1; |
| 295 | } |
| 296 | |
| 297 | /* Newer microcode updates include a size field, whereas older |
| 298 | * containers set it at 0 and are exactly 2048 bytes long */ |
| 299 | uint32_t total_size = mcu_header->total_size ?: 2048; |
| 300 | |
| 301 | /* Quickly sanity check a prospective microcode update. */ |
Jeremy Compostella | 46ffccd | 2022-09-08 13:47:35 -0700 | [diff] [blame] | 302 | if (total_size < sizeof(*mcu_header) || |
| 303 | total_size > file_length) |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 304 | break; |
| 305 | |
Jeremy Compostella | e1465e2 | 2022-09-08 10:11:53 -0700 | [diff] [blame] | 306 | if (num_mcus == max_fit_entries) { |
| 307 | ERROR("Maximum of FIT entries reached.\n"); |
| 308 | return 1; |
| 309 | } |
| 310 | |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 311 | /* FIXME: Should the checksum be validated? */ |
| 312 | mcus[num_mcus].offset = current_offset; |
| 313 | mcus[num_mcus].size = total_size; |
| 314 | |
| 315 | /* Proceed to next payload. */ |
| 316 | current_offset += mcus[num_mcus].size; |
| 317 | file_length -= mcus[num_mcus].size; |
| 318 | num_mcus++; |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 319 | if (file_length < sizeof(struct microcode_header)) |
| 320 | break; |
| 321 | } |
| 322 | |
| 323 | /* Update how many microcode updates we found. */ |
| 324 | *mcus_found = num_mcus; |
| 325 | |
| 326 | return 0; |
| 327 | } |
| 328 | |
| 329 | /* There can be zero or more FIT_TYPE_MICROCODE entries */ |
Rizwan Qureshi | c1072f2 | 2018-06-04 23:02:46 +0530 | [diff] [blame] | 330 | static void update_fit_ucode_entry(struct fit_table *fit, |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 331 | struct fit_entry *entry, |
| 332 | const uint64_t mcu_addr) |
Rizwan Qureshi | c1072f2 | 2018-06-04 23:02:46 +0530 | [diff] [blame] | 333 | { |
| 334 | entry->address = mcu_addr; |
| 335 | /* |
| 336 | * While loading MCU, its size is not referred from FIT and |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 337 | * rather from the MCU header, hence we can assign zero here. |
Rizwan Qureshi | c1072f2 | 2018-06-04 23:02:46 +0530 | [diff] [blame] | 338 | */ |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 339 | entry->size_reserved = 0; |
Matt DeVillier | 0a36c2c | 2018-06-28 13:21:10 -0500 | [diff] [blame] | 340 | entry->type_checksum_valid = FIT_TYPE_MICROCODE; |
Rizwan Qureshi | c1072f2 | 2018-06-04 23:02:46 +0530 | [diff] [blame] | 341 | entry->version = FIT_MICROCODE_VERSION; |
| 342 | entry->checksum = 0; |
| 343 | fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); |
| 344 | } |
| 345 | |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 346 | /* |
| 347 | * There can be zero or one FIT_TYPE_BIOS_ACM entry per table. |
| 348 | * In case there's a FIT_TYPE_BIOS_ACM entry, at least one |
| 349 | * FIT_TYPE_BIOS_STARTUP entry must exist. |
| 350 | * |
| 351 | * The caller has to provide valid arguments as those aren't verfied. |
| 352 | */ |
| 353 | static void update_fit_bios_acm_entry(struct fit_table *fit, |
| 354 | struct fit_entry *entry, |
| 355 | const uint64_t acm_addr) |
| 356 | { |
| 357 | entry->address = acm_addr; |
| 358 | /* |
| 359 | * The Address field points to a BIOS ACM. The Address field points to |
| 360 | * the first byte of the AC module header. When BIOS ACM is loaded in |
| 361 | * Authenticated Code RAM, one MTRR base/limit pair is used to map it. |
| 362 | */ |
| 363 | entry->size_reserved = 0; |
| 364 | entry->type_checksum_valid = FIT_TYPE_BIOS_ACM; |
| 365 | entry->version = FIT_TXT_VERSION; |
| 366 | entry->checksum = 0; |
| 367 | fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); |
| 368 | } |
| 369 | |
| 370 | /* |
| 371 | * In case there's a FIT_TYPE_BIOS_ACM entry, at least one |
| 372 | * FIT_TYPE_BIOS_STARTUP entry must exist. |
| 373 | * |
| 374 | * The caller has to provide valid arguments as those aren't verfied. |
| 375 | */ |
| 376 | static void update_fit_bios_startup_entry(struct fit_table *fit, |
| 377 | struct fit_entry *entry, |
| 378 | const uint64_t sm_addr, |
| 379 | const uint32_t sm_size) |
| 380 | { |
| 381 | entry->address = sm_addr; |
| 382 | assert(sm_size % 16 == 0); |
| 383 | /* |
| 384 | * BIOS Startup code is defined as the code that gets control at the |
| 385 | * reset vector and continues the chain of trust in TCG-compliant |
| 386 | * fashion. In addition, this code may also configure memory and SMRAM. |
| 387 | */ |
| 388 | fit_entry_update_size(entry, sm_size); |
| 389 | entry->type_checksum_valid = FIT_TYPE_BIOS_STARTUP; |
| 390 | entry->version = FIT_TXT_VERSION; |
| 391 | entry->checksum = 0; |
| 392 | fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); |
| 393 | } |
| 394 | |
| 395 | /* |
| 396 | * There can be zero or one FIT_TYPE_BIOS_POLICY Record in the FIT. |
| 397 | * If the platform uses the hash comparison method and employs a |
| 398 | * failsafe bootblock, one FIT_TYPE_BIOS_POLICY entry is needed to |
| 399 | * contain the failsafe hash. |
| 400 | * If the platform uses the Signature verification method, one |
| 401 | * FIT_TYPE_BIOS_POLICY entry is needed. In this case, the entry |
| 402 | * contains the OEM key, hash of the BIOS and signature over the hash |
| 403 | * using the OEM key. |
| 404 | * In all other cases, the FIT_TYPE_BIOS_POLICY record is not required. |
| 405 | * |
| 406 | * The caller has to provide valid arguments as those aren't verfied. |
| 407 | */ |
| 408 | static void update_fit_bios_policy_entry(struct fit_table *fit, |
| 409 | struct fit_entry *entry, |
| 410 | const uint64_t lcp_policy_addr, |
| 411 | const uint32_t lcp_policy_size) |
| 412 | { |
| 413 | entry->address = lcp_policy_addr; |
| 414 | fit_entry_update_size(entry, lcp_policy_size); |
| 415 | entry->type_checksum_valid = FIT_TYPE_BIOS_POLICY; |
| 416 | entry->version = FIT_TXT_VERSION; |
| 417 | entry->checksum = 0; |
| 418 | fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); |
| 419 | } |
| 420 | |
| 421 | /* |
| 422 | * There can be zero or one FIT_TYPE_TXT_POLICY entries |
| 423 | * |
| 424 | * The caller has to provide valid arguments as those aren't verfied. |
| 425 | */ |
| 426 | static void update_fit_txt_policy_entry(struct fit_table *fit, |
| 427 | struct fit_entry *entry, |
| 428 | uint64_t txt_policy_addr) |
| 429 | { |
| 430 | entry->address = txt_policy_addr; |
| 431 | /* |
| 432 | * Points to the flag indicating if TXT is enabled on this platform. |
| 433 | * If not present, TXT is not disabled by FIT. |
| 434 | */ |
| 435 | entry->size_reserved = 0; |
| 436 | entry->type_checksum_valid = FIT_TYPE_TXT_POLICY; |
| 437 | entry->version = 0x1; |
| 438 | entry->checksum = 0; |
| 439 | fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); |
| 440 | } |
| 441 | |
Michał Żygowski | 7ed4039 | 2020-07-11 15:50:55 +0200 | [diff] [blame] | 442 | /* |
| 443 | * There can be zero or one FIT_TYPE_BOOT_POLICY entries |
| 444 | * |
| 445 | * The caller has to provide valid arguments as those aren't verified. |
| 446 | */ |
| 447 | static void update_fit_boot_policy_entry(struct fit_table *fit, |
| 448 | struct fit_entry *entry, |
| 449 | uint64_t boot_policy_addr, |
| 450 | uint32_t boot_policy_size) |
| 451 | { |
| 452 | entry->address = boot_policy_addr; |
| 453 | entry->type_checksum_valid = FIT_TYPE_BOOT_POLICY; |
| 454 | entry->size_reserved = boot_policy_size; |
| 455 | entry->version = FIT_TXT_VERSION; |
| 456 | entry->checksum = 0; |
| 457 | fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); |
| 458 | } |
| 459 | |
| 460 | /* |
| 461 | * There can be zero or one FIT_TYPE_KEY_MANIFEST entries |
| 462 | * |
| 463 | * The caller has to provide valid arguments as those aren't verified. |
| 464 | */ |
| 465 | static void update_fit_key_manifest_entry(struct fit_table *fit, |
| 466 | struct fit_entry *entry, |
| 467 | uint64_t key_manifest_addr, |
| 468 | uint32_t key_manifest_size) |
| 469 | { |
| 470 | entry->address = key_manifest_addr; |
| 471 | |
| 472 | entry->type_checksum_valid = FIT_TYPE_KEY_MANIFEST; |
| 473 | entry->size_reserved = key_manifest_size; |
| 474 | entry->version = FIT_TXT_VERSION; |
| 475 | entry->checksum = 0; |
| 476 | fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); |
| 477 | } |
| 478 | |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 479 | /* Special case for ucode CBFS file, as it might contain more than one ucode */ |
| 480 | int fit_add_microcode_file(struct fit_table *fit, |
| 481 | struct cbfs_image *image, |
| 482 | const char *blob_name, |
| 483 | fit_offset_converter_t offset_helper, |
| 484 | const size_t max_fit_entries) |
| 485 | { |
| 486 | struct microcode_entry *mcus; |
| 487 | |
| 488 | size_t i; |
| 489 | size_t mcus_found; |
| 490 | |
| 491 | mcus = malloc(sizeof(*mcus) * max_fit_entries); |
| 492 | if (!mcus) { |
| 493 | ERROR("Couldn't allocate memory for microcode entries.\n"); |
| 494 | return 1; |
| 495 | } |
| 496 | |
| 497 | if (parse_microcode_blob(image, blob_name, &mcus_found, mcus, |
| 498 | max_fit_entries)) { |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 499 | free(mcus); |
| 500 | return 1; |
| 501 | } |
| 502 | |
| 503 | for (i = 0; i < mcus_found; i++) { |
| 504 | if (fit_add_entry(fit, |
| 505 | offset_to_ptr(offset_helper, &image->buffer, |
| 506 | mcus[i].offset), |
| 507 | 0, |
| 508 | FIT_TYPE_MICROCODE, |
| 509 | max_fit_entries)) { |
| 510 | |
| 511 | free(mcus); |
| 512 | return 1; |
| 513 | } |
| 514 | } |
| 515 | |
| 516 | free(mcus); |
| 517 | return 0; |
| 518 | } |
| 519 | |
Arthur Heymans | e9e4e54 | 2021-02-17 17:34:44 +0100 | [diff] [blame] | 520 | static uint32_t *get_fit_ptr(struct buffer *bootblock, fit_offset_converter_t offset_fn, |
| 521 | uint32_t topswap_size) |
| 522 | { |
| 523 | return rom_buffer_pointer(bootblock, |
| 524 | ptr_to_offset(offset_fn, bootblock, |
| 525 | FIT_POINTER_LOCATION - topswap_size)); |
| 526 | } |
| 527 | |
| 528 | /* Set the FIT pointer to a FIT table. */ |
| 529 | int set_fit_pointer(struct buffer *bootblock, |
| 530 | const uint32_t fit_address, |
| 531 | fit_offset_converter_t offset_fn, |
| 532 | uint32_t topswap_size) |
| 533 | { |
| 534 | struct fit_table *fit; |
| 535 | uint32_t *fit_pointer = get_fit_ptr(bootblock, offset_fn, topswap_size); |
| 536 | |
| 537 | fit = rom_buffer_pointer(bootblock, ptr_to_offset(offset_fn, bootblock, fit_address)); |
| 538 | |
| 539 | if (fit_address < FIT_TABLE_LOWEST_ADDRESS) { |
| 540 | ERROR("FIT must be reside in the top 16MiB.\n"); |
| 541 | return 1; |
| 542 | } |
| 543 | |
| 544 | if (!fit_table_verified(fit)) { |
| 545 | ERROR("FIT not found at address.\n"); |
| 546 | return 1; |
| 547 | } |
| 548 | |
| 549 | fit_pointer[0] = fit_address; |
| 550 | fit_pointer[1] = 0; |
| 551 | return 0; |
| 552 | } |
| 553 | |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 554 | /* |
| 555 | * Return a pointer to the active FIT. |
| 556 | */ |
| 557 | struct fit_table *fit_get_table(struct buffer *bootblock, |
| 558 | fit_offset_converter_t offset_fn, |
| 559 | uint32_t topswap_size) |
| 560 | { |
| 561 | struct fit_table *fit; |
Arthur Heymans | f2b7104 | 2021-05-19 15:40:10 +0200 | [diff] [blame] | 562 | uint32_t *fit_pointer = get_fit_ptr(bootblock, offset_fn, topswap_size); |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 563 | |
| 564 | /* Ensure pointer is below 4GiB and within 16MiB of 4GiB */ |
| 565 | if (fit_pointer[1] != 0 || fit_pointer[0] < FIT_TABLE_LOWEST_ADDRESS) { |
| 566 | ERROR("FIT not found.\n"); |
| 567 | return NULL; |
| 568 | } |
| 569 | |
| 570 | fit = rom_buffer_pointer(bootblock, |
| 571 | ptr_to_offset(offset_fn, bootblock, *fit_pointer)); |
| 572 | if (!fit_table_verified(fit)) { |
| 573 | ERROR("FIT not found.\n"); |
| 574 | return NULL; |
| 575 | } |
| 576 | |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 577 | return fit; |
| 578 | } |
| 579 | |
| 580 | /* |
| 581 | * Dump the current FIT in human readable format to stdout. |
| 582 | */ |
| 583 | int fit_dump(struct fit_table *fit) |
| 584 | { |
| 585 | size_t i; |
| 586 | |
| 587 | if (!fit) |
| 588 | return 1; |
| 589 | |
| 590 | printf("\n"); |
| 591 | printf(" FIT table:\n"); |
| 592 | |
| 593 | if (fit_table_entries(fit) < 1) { |
| 594 | printf(" empty\n\n"); |
| 595 | return 0; |
| 596 | } |
| 597 | |
| 598 | printf(" %-6s %-20s %-16s %-8s\n", "Index", "Type", "Addr", "Size"); |
| 599 | |
| 600 | for (i = 0; i < fit_table_entries(fit); i++) { |
| 601 | const char *name; |
| 602 | |
| 603 | switch (fit->entries[i].type_checksum_valid) { |
| 604 | case FIT_TYPE_MICROCODE: |
| 605 | name = "Microcode"; |
| 606 | break; |
| 607 | case FIT_TYPE_BIOS_ACM: |
| 608 | name = "BIOS ACM"; |
| 609 | break; |
| 610 | case FIT_TYPE_BIOS_STARTUP: |
| 611 | name = "BIOS Startup Module"; |
| 612 | break; |
| 613 | case FIT_TYPE_TPM_POLICY: |
| 614 | name = "TPM Policy"; |
| 615 | break; |
| 616 | case FIT_TYPE_BIOS_POLICY: |
| 617 | name = "BIOS Policy"; |
| 618 | break; |
| 619 | case FIT_TYPE_TXT_POLICY: |
| 620 | name = "TXT Policy"; |
| 621 | break; |
| 622 | case FIT_TYPE_KEY_MANIFEST: |
| 623 | name = "Key Manifest"; |
| 624 | break; |
| 625 | case FIT_TYPE_BOOT_POLICY: |
| 626 | name = "Boot Policy"; |
| 627 | break; |
| 628 | case FIT_TYPE_CSE_SECURE_BOOT: |
| 629 | name = "CSE SecureBoot"; |
| 630 | break; |
| 631 | case FIT_TYPE_TXTSX_POLICY: |
| 632 | name = "TXTSX policy"; |
| 633 | break; |
| 634 | case FIT_TYPE_JMP_DEBUG_POLICY: |
| 635 | name = "JMP debug policy"; |
| 636 | break; |
| 637 | case FIT_TYPE_UNUSED: |
| 638 | name = "unused"; |
| 639 | break; |
| 640 | default: |
| 641 | name = "unknown"; |
| 642 | } |
| 643 | |
| 644 | printf(" %6zd %-20s 0x%08"PRIx64" 0x%08zx\n", i, name, |
| 645 | fit->entries[i].address, |
| 646 | fit_entry_size_bytes(&fit->entries[i])); |
| 647 | } |
| 648 | printf("\n"); |
| 649 | return 0; |
| 650 | } |
| 651 | |
| 652 | /* |
| 653 | * Remove all entries from table. |
| 654 | */ |
| 655 | int fit_clear_table(struct fit_table *fit) |
| 656 | { |
| 657 | if (!fit) |
| 658 | return 1; |
| 659 | |
| 660 | memset(fit->entries, 0, |
| 661 | sizeof(struct fit_entry) * fit_table_entries(fit)); |
| 662 | |
| 663 | /* Reset entry counter in header */ |
| 664 | fit_entry_update_size(&fit->header, sizeof(fit->header)); |
| 665 | |
| 666 | update_fit_checksum(fit); |
| 667 | |
| 668 | return 0; |
| 669 | } |
| 670 | |
| 671 | /* |
| 672 | * Returns true if the FIT type is know and can be added to the table. |
| 673 | */ |
| 674 | int fit_is_supported_type(const enum fit_type type) |
| 675 | { |
| 676 | switch (type) { |
| 677 | case FIT_TYPE_MICROCODE: |
| 678 | case FIT_TYPE_BIOS_ACM: |
| 679 | case FIT_TYPE_BIOS_STARTUP: |
| 680 | case FIT_TYPE_BIOS_POLICY: |
| 681 | case FIT_TYPE_TXT_POLICY: |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 682 | case FIT_TYPE_KEY_MANIFEST: |
| 683 | case FIT_TYPE_BOOT_POLICY: |
Michał Żygowski | 7ed4039 | 2020-07-11 15:50:55 +0200 | [diff] [blame] | 684 | return 1; |
| 685 | case FIT_TYPE_TPM_POLICY: |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 686 | default: |
| 687 | return 0; |
| 688 | } |
| 689 | } |
| 690 | |
| 691 | /* |
| 692 | * Adds an known entry to the FIT. |
| 693 | * len is optional for same types and might be zero. |
| 694 | * offset is an absolute address in 32-bit protected mode address space. |
| 695 | */ |
| 696 | int fit_add_entry(struct fit_table *fit, |
| 697 | const uint32_t offset, |
| 698 | const uint32_t len, |
| 699 | const enum fit_type type, |
| 700 | const size_t max_fit_entries) |
| 701 | { |
| 702 | struct fit_entry *entry; |
| 703 | |
| 704 | if (!fit) { |
| 705 | ERROR("Internal error."); |
| 706 | return 1; |
| 707 | } |
| 708 | |
| 709 | if (fit_free_space(fit, max_fit_entries) < 1) { |
| 710 | ERROR("No space left in FIT."); |
| 711 | return 1; |
| 712 | } |
| 713 | |
| 714 | if (!fit_is_supported_type(type)) { |
| 715 | ERROR("Unsupported FIT type %u\n", type); |
| 716 | return 1; |
| 717 | } |
| 718 | |
| 719 | DEBUG("Adding new entry type %u at offset %zd\n", type, |
| 720 | fit_table_entries(fit)); |
| 721 | |
| 722 | entry = get_next_free_entry(fit); |
| 723 | |
| 724 | switch (type) { |
| 725 | case FIT_TYPE_MICROCODE: |
| 726 | update_fit_ucode_entry(fit, entry, offset); |
| 727 | break; |
| 728 | case FIT_TYPE_BIOS_ACM: |
| 729 | update_fit_bios_acm_entry(fit, entry, offset); |
| 730 | break; |
| 731 | case FIT_TYPE_BIOS_STARTUP: |
| 732 | update_fit_bios_startup_entry(fit, entry, offset, len); |
| 733 | break; |
| 734 | case FIT_TYPE_BIOS_POLICY: |
| 735 | update_fit_bios_policy_entry(fit, entry, offset, len); |
| 736 | break; |
| 737 | case FIT_TYPE_TXT_POLICY: |
| 738 | update_fit_txt_policy_entry(fit, entry, offset); |
| 739 | break; |
Michał Żygowski | 7ed4039 | 2020-07-11 15:50:55 +0200 | [diff] [blame] | 740 | case FIT_TYPE_KEY_MANIFEST: |
| 741 | update_fit_key_manifest_entry(fit, entry, offset, len); |
| 742 | break; |
| 743 | case FIT_TYPE_BOOT_POLICY: |
| 744 | update_fit_boot_policy_entry(fit, entry, offset, len); |
| 745 | break; |
Philipp Deppenwiese | 5ada002 | 2018-11-20 13:54:49 +0100 | [diff] [blame] | 746 | default: |
| 747 | return 1; |
| 748 | } |
| 749 | |
| 750 | sort_fit_table(fit); |
| 751 | |
| 752 | update_fit_checksum(fit); |
| 753 | |
| 754 | return 0; |
| 755 | } |
| 756 | |
| 757 | /* |
| 758 | * Delete one entry from table. |
| 759 | */ |
| 760 | int fit_delete_entry(struct fit_table *fit, |
| 761 | const size_t idx) |
| 762 | { |
| 763 | if (!fit) { |
| 764 | ERROR("Internal error."); |
| 765 | return 1; |
| 766 | } |
| 767 | |
| 768 | if (idx >= fit_table_entries(fit)) { |
| 769 | ERROR("Index out of range."); |
| 770 | return 1; |
| 771 | } |
| 772 | |
| 773 | memset(&fit->entries[idx], 0, sizeof(struct fit_entry)); |
| 774 | |
| 775 | fit->entries[idx].type_checksum_valid = FIT_TYPE_UNUSED; |
| 776 | |
| 777 | sort_fit_table(fit); |
| 778 | |
| 779 | /* The unused entry is now the last one */ |
| 780 | fit_entry_add_size(&fit->header, -(int)sizeof(struct fit_entry)); |
| 781 | |
| 782 | update_fit_checksum(fit); |
| 783 | |
| 784 | return 0; |
| 785 | } |