| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright(c) 2022 Intel Corporation. */ |
| |
| #include <linux/firmware.h> |
| #include <linux/sizes.h> |
| #include <asm/cpu.h> |
| #include <asm/microcode.h> |
| |
| #include "ifs.h" |
| |
| #define IFS_CHUNK_ALIGNMENT 256 |
| union meta_data { |
| struct { |
| u32 meta_type; // metadata type |
| u32 meta_size; // size of this entire struct including hdrs. |
| u32 test_type; // IFS test type |
| u32 fusa_info; // Fusa info |
| u32 total_images; // Total number of images |
| u32 current_image; // Current Image # |
| u32 total_chunks; // Total number of chunks in this image |
| u32 starting_chunk; // Starting chunk number in this image |
| u32 size_per_chunk; // size of each chunk |
| u32 chunks_per_stride; // number of chunks in a stride |
| }; |
| u8 padding[IFS_CHUNK_ALIGNMENT]; |
| }; |
| |
| #define IFS_HEADER_SIZE (sizeof(struct microcode_header_intel)) |
| #define META_TYPE_IFS 1 |
| #define INVALIDATE_STRIDE 0x1UL |
| #define IFS_GEN_STRIDE_AWARE 2 |
| #define AUTH_INTERRUPTED_ERROR 5 |
| #define IFS_AUTH_RETRY_CT 10 |
| |
| static struct microcode_header_intel *ifs_header_ptr; /* pointer to the ifs image header */ |
| static u64 ifs_hash_ptr; /* Address of ifs metadata (hash) */ |
| static u64 ifs_test_image_ptr; /* 256B aligned address of test pattern */ |
| static DECLARE_COMPLETION(ifs_done); |
| |
| static const char * const scan_hash_status[] = { |
| [0] = "No error reported", |
| [1] = "Attempt to copy scan hashes when copy already in progress", |
| [2] = "Secure Memory not set up correctly", |
| [3] = "FuSaInfo.ProgramID does not match or ff-mm-ss does not match", |
| [4] = "Reserved", |
| [5] = "Integrity check failed", |
| [6] = "Scan reload or test is in progress" |
| }; |
| |
| static const char * const scan_authentication_status[] = { |
| [0] = "No error reported", |
| [1] = "Attempt to authenticate a chunk which is already marked as authentic", |
| [2] = "Chunk authentication error. The hash of chunk did not match expected value", |
| [3] = "Reserved", |
| [4] = "Chunk outside the current stride", |
| [5] = "Authentication flow interrupted", |
| }; |
| |
| #define MC_HEADER_META_TYPE_END (0) |
| |
| struct metadata_header { |
| unsigned int type; |
| unsigned int blk_size; |
| }; |
| |
| static struct metadata_header *find_meta_data(void *ucode, unsigned int meta_type) |
| { |
| struct microcode_header_intel *hdr = &((struct microcode_intel *)ucode)->hdr; |
| struct metadata_header *meta_header; |
| unsigned long data_size, total_meta; |
| unsigned long meta_size = 0; |
| |
| data_size = intel_microcode_get_datasize(hdr); |
| total_meta = hdr->metasize; |
| if (!total_meta) |
| return NULL; |
| |
| meta_header = (ucode + MC_HEADER_SIZE + data_size) - total_meta; |
| |
| while (meta_header->type != MC_HEADER_META_TYPE_END && |
| meta_header->blk_size && |
| meta_size < total_meta) { |
| meta_size += meta_header->blk_size; |
| if (meta_header->type == meta_type) |
| return meta_header; |
| |
| meta_header = (void *)meta_header + meta_header->blk_size; |
| } |
| return NULL; |
| } |
| |
| static void hashcopy_err_message(struct device *dev, u32 err_code) |
| { |
| if (err_code >= ARRAY_SIZE(scan_hash_status)) |
| dev_err(dev, "invalid error code 0x%x for hash copy\n", err_code); |
| else |
| dev_err(dev, "Hash copy error : %s\n", scan_hash_status[err_code]); |
| } |
| |
| static void auth_err_message(struct device *dev, u32 err_code) |
| { |
| if (err_code >= ARRAY_SIZE(scan_authentication_status)) |
| dev_err(dev, "invalid error code 0x%x for authentication\n", err_code); |
| else |
| dev_err(dev, "Chunk authentication error : %s\n", |
| scan_authentication_status[err_code]); |
| } |
| |
| /* |
| * To copy scan hashes and authenticate test chunks, the initiating cpu must point |
| * to the EDX:EAX to the test image in linear address. |
| * Run wrmsr(MSR_COPY_SCAN_HASHES) for scan hash copy and run wrmsr(MSR_AUTHENTICATE_AND_COPY_CHUNK) |
| * for scan hash copy and test chunk authentication. |
| */ |
| static void copy_hashes_authenticate_chunks(struct work_struct *work) |
| { |
| struct ifs_work *local_work = container_of(work, struct ifs_work, w); |
| union ifs_scan_hashes_status hashes_status; |
| union ifs_chunks_auth_status chunk_status; |
| struct device *dev = local_work->dev; |
| const struct ifs_test_msrs *msrs; |
| int i, num_chunks, chunk_size; |
| struct ifs_data *ifsd; |
| u64 linear_addr, base; |
| u32 err_code; |
| |
| ifsd = ifs_get_data(dev); |
| msrs = ifs_get_test_msrs(dev); |
| /* run scan hash copy */ |
| wrmsrl(msrs->copy_hashes, ifs_hash_ptr); |
| rdmsrl(msrs->copy_hashes_status, hashes_status.data); |
| |
| /* enumerate the scan image information */ |
| num_chunks = hashes_status.num_chunks; |
| chunk_size = hashes_status.chunk_size * 1024; |
| err_code = hashes_status.error_code; |
| |
| if (!hashes_status.valid) { |
| ifsd->loading_error = true; |
| hashcopy_err_message(dev, err_code); |
| goto done; |
| } |
| |
| /* base linear address to the scan data */ |
| base = ifs_test_image_ptr; |
| |
| /* scan data authentication and copy chunks to secured memory */ |
| for (i = 0; i < num_chunks; i++) { |
| linear_addr = base + i * chunk_size; |
| linear_addr |= i; |
| |
| wrmsrl(msrs->copy_chunks, linear_addr); |
| rdmsrl(msrs->copy_chunks_status, chunk_status.data); |
| |
| ifsd->valid_chunks = chunk_status.valid_chunks; |
| err_code = chunk_status.error_code; |
| |
| if (err_code) { |
| ifsd->loading_error = true; |
| auth_err_message(dev, err_code); |
| goto done; |
| } |
| } |
| done: |
| complete(&ifs_done); |
| } |
| |
| static int get_num_chunks(int gen, union ifs_scan_hashes_status_gen2 status) |
| { |
| return gen >= IFS_GEN_STRIDE_AWARE ? status.chunks_in_stride : status.num_chunks; |
| } |
| |
| static bool need_copy_scan_hashes(struct ifs_data *ifsd) |
| { |
| return !ifsd->loaded || |
| ifsd->generation < IFS_GEN_STRIDE_AWARE || |
| ifsd->loaded_version != ifs_header_ptr->rev; |
| } |
| |
| static int copy_hashes_authenticate_chunks_gen2(struct device *dev) |
| { |
| union ifs_scan_hashes_status_gen2 hashes_status; |
| union ifs_chunks_auth_status_gen2 chunk_status; |
| u32 err_code, valid_chunks, total_chunks; |
| const struct ifs_test_msrs *msrs; |
| int i, num_chunks, chunk_size; |
| union meta_data *ifs_meta; |
| int starting_chunk_nr; |
| struct ifs_data *ifsd; |
| u64 linear_addr, base; |
| u64 chunk_table[2]; |
| int retry_count; |
| |
| ifsd = ifs_get_data(dev); |
| msrs = ifs_get_test_msrs(dev); |
| |
| if (need_copy_scan_hashes(ifsd)) { |
| wrmsrl(msrs->copy_hashes, ifs_hash_ptr); |
| rdmsrl(msrs->copy_hashes_status, hashes_status.data); |
| |
| /* enumerate the scan image information */ |
| chunk_size = hashes_status.chunk_size * SZ_1K; |
| err_code = hashes_status.error_code; |
| |
| num_chunks = get_num_chunks(ifsd->generation, hashes_status); |
| |
| if (!hashes_status.valid) { |
| hashcopy_err_message(dev, err_code); |
| return -EIO; |
| } |
| ifsd->loaded_version = ifs_header_ptr->rev; |
| ifsd->chunk_size = chunk_size; |
| } else { |
| num_chunks = ifsd->valid_chunks; |
| chunk_size = ifsd->chunk_size; |
| } |
| |
| if (ifsd->generation >= IFS_GEN_STRIDE_AWARE) { |
| wrmsrl(msrs->test_ctrl, INVALIDATE_STRIDE); |
| rdmsrl(msrs->copy_chunks_status, chunk_status.data); |
| if (chunk_status.valid_chunks != 0) { |
| dev_err(dev, "Couldn't invalidate installed stride - %d\n", |
| chunk_status.valid_chunks); |
| return -EIO; |
| } |
| } |
| |
| base = ifs_test_image_ptr; |
| ifs_meta = (union meta_data *)find_meta_data(ifs_header_ptr, META_TYPE_IFS); |
| starting_chunk_nr = ifs_meta->starting_chunk; |
| |
| /* scan data authentication and copy chunks to secured memory */ |
| for (i = 0; i < num_chunks; i++) { |
| retry_count = IFS_AUTH_RETRY_CT; |
| linear_addr = base + i * chunk_size; |
| |
| chunk_table[0] = starting_chunk_nr + i; |
| chunk_table[1] = linear_addr; |
| do { |
| local_irq_disable(); |
| wrmsrl(msrs->copy_chunks, (u64)chunk_table); |
| local_irq_enable(); |
| rdmsrl(msrs->copy_chunks_status, chunk_status.data); |
| err_code = chunk_status.error_code; |
| } while (err_code == AUTH_INTERRUPTED_ERROR && --retry_count); |
| |
| if (err_code) { |
| ifsd->loading_error = true; |
| auth_err_message(dev, err_code); |
| return -EIO; |
| } |
| } |
| |
| valid_chunks = chunk_status.valid_chunks; |
| total_chunks = chunk_status.total_chunks; |
| |
| if (valid_chunks != total_chunks) { |
| ifsd->loading_error = true; |
| dev_err(dev, "Couldn't authenticate all the chunks. Authenticated %d total %d.\n", |
| valid_chunks, total_chunks); |
| return -EIO; |
| } |
| ifsd->valid_chunks = valid_chunks; |
| ifsd->max_bundle = chunk_status.max_bundle; |
| |
| return 0; |
| } |
| |
| static int validate_ifs_metadata(struct device *dev) |
| { |
| const struct ifs_test_caps *test = ifs_get_test_caps(dev); |
| struct ifs_data *ifsd = ifs_get_data(dev); |
| union meta_data *ifs_meta; |
| char test_file[64]; |
| int ret = -EINVAL; |
| |
| snprintf(test_file, sizeof(test_file), "%02x-%02x-%02x-%02x.%s", |
| boot_cpu_data.x86, boot_cpu_data.x86_model, |
| boot_cpu_data.x86_stepping, ifsd->cur_batch, test->image_suffix); |
| |
| ifs_meta = (union meta_data *)find_meta_data(ifs_header_ptr, META_TYPE_IFS); |
| if (!ifs_meta) { |
| dev_err(dev, "IFS Metadata missing in file %s\n", test_file); |
| return ret; |
| } |
| |
| ifs_test_image_ptr = (u64)ifs_meta + sizeof(union meta_data); |
| |
| /* Scan chunk start must be 256 byte aligned */ |
| if (!IS_ALIGNED(ifs_test_image_ptr, IFS_CHUNK_ALIGNMENT)) { |
| dev_err(dev, "Scan pattern is not aligned on %d bytes aligned in %s\n", |
| IFS_CHUNK_ALIGNMENT, test_file); |
| return ret; |
| } |
| |
| if (ifs_meta->current_image != ifsd->cur_batch) { |
| dev_warn(dev, "Mismatch between filename %s and batch metadata 0x%02x\n", |
| test_file, ifs_meta->current_image); |
| return ret; |
| } |
| |
| if (ifs_meta->chunks_per_stride && |
| (ifs_meta->starting_chunk % ifs_meta->chunks_per_stride != 0)) { |
| dev_warn(dev, "Starting chunk num %u not a multiple of chunks_per_stride %u\n", |
| ifs_meta->starting_chunk, ifs_meta->chunks_per_stride); |
| return ret; |
| } |
| |
| if (ifs_meta->test_type != test->test_num) { |
| dev_warn(dev, "Metadata test_type %d mismatches with device type\n", |
| ifs_meta->test_type); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * IFS requires scan chunks authenticated per each socket in the platform. |
| * Once the test chunk is authenticated, it is automatically copied to secured memory |
| * and proceed the authentication for the next chunk. |
| */ |
| static int scan_chunks_sanity_check(struct device *dev) |
| { |
| struct ifs_data *ifsd = ifs_get_data(dev); |
| struct ifs_work local_work; |
| int curr_pkg, cpu, ret; |
| |
| memset(ifs_pkg_auth, 0, (topology_max_packages() * sizeof(bool))); |
| ret = validate_ifs_metadata(dev); |
| if (ret) |
| return ret; |
| |
| ifsd->loading_error = false; |
| |
| if (ifsd->generation > 0) |
| return copy_hashes_authenticate_chunks_gen2(dev); |
| |
| /* copy the scan hash and authenticate per package */ |
| cpus_read_lock(); |
| for_each_online_cpu(cpu) { |
| curr_pkg = topology_physical_package_id(cpu); |
| if (ifs_pkg_auth[curr_pkg]) |
| continue; |
| reinit_completion(&ifs_done); |
| local_work.dev = dev; |
| INIT_WORK_ONSTACK(&local_work.w, copy_hashes_authenticate_chunks); |
| schedule_work_on(cpu, &local_work.w); |
| wait_for_completion(&ifs_done); |
| if (ifsd->loading_error) { |
| ret = -EIO; |
| goto out; |
| } |
| ifs_pkg_auth[curr_pkg] = 1; |
| } |
| ret = 0; |
| ifsd->loaded_version = ifs_header_ptr->rev; |
| out: |
| cpus_read_unlock(); |
| |
| return ret; |
| } |
| |
| static int image_sanity_check(struct device *dev, const struct microcode_header_intel *data) |
| { |
| struct cpu_signature sig; |
| |
| /* Provide a specific error message when loading an older/unsupported image */ |
| if (data->hdrver != MC_HEADER_TYPE_IFS) { |
| dev_err(dev, "Header version %d not supported\n", data->hdrver); |
| return -EINVAL; |
| } |
| |
| if (intel_microcode_sanity_check((void *)data, true, MC_HEADER_TYPE_IFS)) { |
| dev_err(dev, "sanity check failed\n"); |
| return -EINVAL; |
| } |
| |
| intel_collect_cpu_info(&sig); |
| |
| if (!intel_find_matching_signature((void *)data, &sig)) { |
| dev_err(dev, "cpu signature, processor flags not matching\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Load ifs image. Before loading ifs module, the ifs image must be located |
| * in /lib/firmware/intel/ifs_x/ and named as family-model-stepping-02x.{testname}. |
| */ |
| int ifs_load_firmware(struct device *dev) |
| { |
| const struct ifs_test_caps *test = ifs_get_test_caps(dev); |
| struct ifs_data *ifsd = ifs_get_data(dev); |
| unsigned int expected_size; |
| const struct firmware *fw; |
| char scan_path[64]; |
| int ret; |
| |
| snprintf(scan_path, sizeof(scan_path), "intel/ifs_%d/%02x-%02x-%02x-%02x.%s", |
| test->test_num, boot_cpu_data.x86, boot_cpu_data.x86_model, |
| boot_cpu_data.x86_stepping, ifsd->cur_batch, test->image_suffix); |
| |
| ret = request_firmware_direct(&fw, scan_path, dev); |
| if (ret) { |
| dev_err(dev, "ifs file %s load failed\n", scan_path); |
| goto done; |
| } |
| |
| expected_size = ((struct microcode_header_intel *)fw->data)->totalsize; |
| if (fw->size != expected_size) { |
| dev_err(dev, "File size mismatch (expected %u, actual %zu). Corrupted IFS image.\n", |
| expected_size, fw->size); |
| ret = -EINVAL; |
| goto release; |
| } |
| |
| ret = image_sanity_check(dev, (struct microcode_header_intel *)fw->data); |
| if (ret) |
| goto release; |
| |
| ifs_header_ptr = (struct microcode_header_intel *)fw->data; |
| ifs_hash_ptr = (u64)(ifs_header_ptr + 1); |
| |
| ret = scan_chunks_sanity_check(dev); |
| if (ret) |
| dev_err(dev, "Load failure for batch: %02x\n", ifsd->cur_batch); |
| |
| release: |
| release_firmware(fw); |
| done: |
| ifsd->loaded = (ret == 0); |
| |
| return ret; |
| } |