#include <linux/firmware.h>
 #include <asm/cpu.h>
+#include <linux/slab.h>
 #include <asm/microcode_intel.h>
 
 #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)
 {
  */
 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;
        ret = request_firmware_direct(&fw, scan_path, dev);
        if (ret) {
                dev_err(dev, "ifs file %s load failed\n", scan_path);
-               return;
+               goto done;
        }
 
-       if (!ifs_image_sanity_check(dev, (struct microcode_header_intel *)fw->data))
+       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);
 }