// SPDX-License-Identifier: GPL-2.0-only /* Copyright(c) 2022 Intel Corporation. */ #include #include #include #include #include "ifs.h" struct ifs_header { u32 header_ver; u32 blob_revision; u32 date; u32 processor_sig; u32 check_sum; u32 loader_rev; u32 processor_flags; u32 metadata_size; u32 total_size; u32 fusa_info; u64 reserved; }; #define IFS_HEADER_SIZE (sizeof(struct ifs_header)) static struct ifs_header *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" }; /* * 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; int i, num_chunks, chunk_size; struct ifs_data *ifsd; u64 linear_addr, base; u32 err_code; ifsd = ifs_get_data(dev); /* run scan hash copy */ wrmsrl(MSR_COPY_SCAN_HASHES, ifs_hash_ptr); rdmsrl(MSR_SCAN_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; if (err_code >= ARRAY_SIZE(scan_hash_status)) { dev_err(dev, "invalid error code 0x%x for hash copy\n", err_code); goto done; } dev_err(dev, "Hash copy error : %s", scan_hash_status[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(MSR_AUTHENTICATE_AND_COPY_CHUNK, linear_addr); rdmsrl(MSR_CHUNKS_AUTHENTICATION_STATUS, chunk_status.data); ifsd->valid_chunks = chunk_status.valid_chunks; err_code = chunk_status.error_code; if (err_code) { ifsd->loading_error = true; if (err_code >= ARRAY_SIZE(scan_authentication_status)) { dev_err(dev, "invalid error code 0x%x for authentication\n", err_code); goto done; } dev_err(dev, "Chunk authentication error %s\n", scan_authentication_status[err_code]); goto done; } } done: complete(&ifs_done); } /* * 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) { int metadata_size, curr_pkg, cpu, ret = -ENOMEM; struct ifs_data *ifsd = ifs_get_data(dev); bool *package_authenticated; struct ifs_work local_work; char *test_ptr; package_authenticated = kcalloc(topology_max_packages(), sizeof(bool), GFP_KERNEL); if (!package_authenticated) return ret; metadata_size = ifs_header_ptr->metadata_size; /* Spec says that if the Meta Data Size = 0 then it should be treated as 2000 */ if (metadata_size == 0) metadata_size = 2000; /* Scan chunk start must be 256 byte aligned */ if ((metadata_size + IFS_HEADER_SIZE) % 256) { dev_err(dev, "Scan pattern offset within the binary is not 256 byte aligned\n"); return -EINVAL; } test_ptr = (char *)ifs_header_ptr + IFS_HEADER_SIZE + metadata_size; ifsd->loading_error = false; ifs_test_image_ptr = (u64)test_ptr; ifsd->loaded_version = ifs_header_ptr->blob_revision; /* copy the scan hash and authenticate per package */ cpus_read_lock(); for_each_online_cpu(cpu) { curr_pkg = topology_physical_package_id(cpu); if (package_authenticated[curr_pkg]) continue; reinit_completion(&ifs_done); local_work.dev = dev; INIT_WORK(&local_work.w, copy_hashes_authenticate_chunks); schedule_work_on(cpu, &local_work.w); wait_for_completion(&ifs_done); if (ifsd->loading_error) goto out; package_authenticated[curr_pkg] = 1; } ret = 0; out: cpus_read_unlock(); kfree(package_authenticated); return ret; } static int ifs_sanity_check(struct device *dev, const struct microcode_header_intel *mc_header) { unsigned long total_size, data_size; u32 sum, *mc; total_size = get_totalsize(mc_header); data_size = get_datasize(mc_header); if ((data_size + MC_HEADER_SIZE > total_size) || (total_size % sizeof(u32))) { dev_err(dev, "bad ifs data file size.\n"); return -EINVAL; } if (mc_header->ldrver != 1 || mc_header->hdrver != 1) { dev_err(dev, "invalid/unknown ifs update format.\n"); return -EINVAL; } mc = (u32 *)mc_header; sum = 0; for (int i = 0; i < total_size / sizeof(u32); i++) sum += mc[i]; if (sum) { dev_err(dev, "bad ifs data checksum, aborting.\n"); return -EINVAL; } return 0; } static bool find_ifs_matching_signature(struct device *dev, struct ucode_cpu_info *uci, const struct microcode_header_intel *shdr) { unsigned int mc_size; mc_size = get_totalsize(shdr); if (!mc_size || ifs_sanity_check(dev, shdr) < 0) { dev_err(dev, "ifs sanity check failure\n"); return false; } if (!intel_cpu_signatures_match(uci->cpu_sig.sig, uci->cpu_sig.pf, shdr->sig, shdr->pf)) { dev_err(dev, "ifs signature, pf not matching\n"); return false; } return true; } static bool ifs_image_sanity_check(struct device *dev, const struct microcode_header_intel *data) { struct ucode_cpu_info uci; intel_cpu_collect_info(&uci); return find_ifs_matching_signature(dev, &uci, data); } /* * Load ifs image. Before loading ifs module, the ifs image must be located * in /lib/firmware/intel/ifs and named as {family/model/stepping}.{testname}. */ void ifs_load_firmware(struct device *dev) { struct ifs_data *ifsd = ifs_get_data(dev); const struct firmware *fw; char scan_path[32]; int ret; snprintf(scan_path, sizeof(scan_path), "intel/ifs/%02x-%02x-%02x.scan", boot_cpu_data.x86, boot_cpu_data.x86_model, boot_cpu_data.x86_stepping); ret = request_firmware_direct(&fw, scan_path, dev); if (ret) { dev_err(dev, "ifs file %s load failed\n", scan_path); goto done; } if (!ifs_image_sanity_check(dev, (struct microcode_header_intel *)fw->data)) { dev_err(dev, "ifs header sanity check failed\n"); goto release; } ifs_header_ptr = (struct ifs_header *)fw->data; ifs_hash_ptr = (u64)(ifs_header_ptr + 1); ret = scan_chunks_sanity_check(dev); release: release_firmware(fw); done: ifsd->loaded = (ret == 0); }