Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,12 @@
config INTEL_IFS
tristate "Intel In Field Scan"
depends on X86 && CPU_SUP_INTEL && 64BIT && SMP
help
Enable support for the In Field Scan capability in select
CPUs. The capability allows for running low level tests via
a scan image distributed by Intel via Github to validate CPU
operation beyond baseline RAS capabilities. To compile this
support as a module, choose M here. The module will be called
intel_ifs.
If unsure, say N.
@@ -0,0 +1,3 @@
obj-$(CONFIG_INTEL_IFS) += intel_ifs.o
intel_ifs-y := core.o load.o runtest.o sysfs.o
@@ -0,0 +1,158 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2022 Intel Corporation. */
#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
#include <asm/cpu_device_id.h>
#include <asm/msr.h>
#include "ifs.h"
#define X86_MATCH(vfm, array_gen) \
X86_MATCH_VFM_FEATURE(vfm, X86_FEATURE_CORE_CAPABILITIES, array_gen)
static const struct x86_cpu_id ifs_cpu_ids[] __initconst = {
X86_MATCH(INTEL_SAPPHIRERAPIDS_X, ARRAY_GEN0),
X86_MATCH(INTEL_EMERALDRAPIDS_X, ARRAY_GEN0),
X86_MATCH(INTEL_GRANITERAPIDS_X, ARRAY_GEN0),
X86_MATCH(INTEL_GRANITERAPIDS_D, ARRAY_GEN0),
X86_MATCH(INTEL_ATOM_CRESTMONT_X, ARRAY_GEN1),
X86_MATCH(INTEL_ATOM_DARKMONT_X, ARRAY_GEN1),
{}
};
MODULE_DEVICE_TABLE(x86cpu, ifs_cpu_ids);
ATTRIBUTE_GROUPS(plat_ifs);
ATTRIBUTE_GROUPS(plat_ifs_array);
bool *ifs_pkg_auth;
static const struct ifs_test_caps scan_test = {
.integrity_cap_bit = MSR_INTEGRITY_CAPS_PERIODIC_BIST_BIT,
.test_num = IFS_TYPE_SAF,
.image_suffix = "scan",
};
static const struct ifs_test_caps array_test = {
.integrity_cap_bit = MSR_INTEGRITY_CAPS_ARRAY_BIST_BIT,
.test_num = IFS_TYPE_ARRAY_BIST,
};
static const struct ifs_test_msrs scan_msrs = {
.copy_hashes = MSR_COPY_SCAN_HASHES,
.copy_hashes_status = MSR_SCAN_HASHES_STATUS,
.copy_chunks = MSR_AUTHENTICATE_AND_COPY_CHUNK,
.copy_chunks_status = MSR_CHUNKS_AUTHENTICATION_STATUS,
.test_ctrl = MSR_SAF_CTRL,
};
static const struct ifs_test_msrs sbaf_msrs = {
.copy_hashes = MSR_COPY_SBAF_HASHES,
.copy_hashes_status = MSR_SBAF_HASHES_STATUS,
.copy_chunks = MSR_AUTHENTICATE_AND_COPY_SBAF_CHUNK,
.copy_chunks_status = MSR_SBAF_CHUNKS_AUTHENTICATION_STATUS,
.test_ctrl = MSR_SBAF_CTRL,
};
static const struct ifs_test_caps sbaf_test = {
.integrity_cap_bit = MSR_INTEGRITY_CAPS_SBAF_BIT,
.test_num = IFS_TYPE_SBAF,
.image_suffix = "sbft",
};
static struct ifs_device ifs_devices[] = {
[IFS_TYPE_SAF] = {
.test_caps = &scan_test,
.test_msrs = &scan_msrs,
.misc = {
.name = "intel_ifs_0",
.minor = MISC_DYNAMIC_MINOR,
.groups = plat_ifs_groups,
},
},
[IFS_TYPE_ARRAY_BIST] = {
.test_caps = &array_test,
.misc = {
.name = "intel_ifs_1",
.minor = MISC_DYNAMIC_MINOR,
.groups = plat_ifs_array_groups,
},
},
[IFS_TYPE_SBAF] = {
.test_caps = &sbaf_test,
.test_msrs = &sbaf_msrs,
.misc = {
.name = "intel_ifs_2",
.minor = MISC_DYNAMIC_MINOR,
.groups = plat_ifs_groups,
},
},
};
#define IFS_NUMTESTS ARRAY_SIZE(ifs_devices)
static void ifs_cleanup(void)
{
int i;
for (i = 0; i < IFS_NUMTESTS; i++) {
if (ifs_devices[i].misc.this_device)
misc_deregister(&ifs_devices[i].misc);
}
kfree(ifs_pkg_auth);
}
static int __init ifs_init(void)
{
const struct x86_cpu_id *m;
u64 msrval;
int i, ret;
m = x86_match_cpu(ifs_cpu_ids);
if (!m)
return -ENODEV;
if (rdmsrq_safe(MSR_IA32_CORE_CAPS, &msrval))
return -ENODEV;
if (!(msrval & MSR_IA32_CORE_CAPS_INTEGRITY_CAPS))
return -ENODEV;
if (rdmsrq_safe(MSR_INTEGRITY_CAPS, &msrval))
return -ENODEV;
ifs_pkg_auth = kmalloc_objs(bool, topology_max_packages());
if (!ifs_pkg_auth)
return -ENOMEM;
for (i = 0; i < IFS_NUMTESTS; i++) {
if (!(msrval & BIT(ifs_devices[i].test_caps->integrity_cap_bit)))
continue;
ifs_devices[i].rw_data.generation = FIELD_GET(MSR_INTEGRITY_CAPS_SAF_GEN_MASK,
msrval);
ifs_devices[i].rw_data.array_gen = (u32)m->driver_data;
ret = misc_register(&ifs_devices[i].misc);
if (ret)
goto err_exit;
}
return 0;
err_exit:
ifs_cleanup();
return ret;
}
static void __exit ifs_exit(void)
{
ifs_cleanup();
}
module_init(ifs_init);
module_exit(ifs_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Intel In Field Scan (IFS) device");
@@ -0,0 +1,417 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright(c) 2022 Intel Corporation. */
#ifndef _IFS_H_
#define _IFS_H_
/**
* DOC: In-Field Scan
*
* =============
* In-Field Scan
* =============
*
* Introduction
* ------------
*
* In Field Scan (IFS) is a hardware feature to run circuit level tests on
* a CPU core to detect problems that are not caught by parity or ECC checks.
* Future CPUs will support more than one type of test which will show up
* with a new platform-device instance-id.
*
*
* IFS Image
* ---------
*
* Intel provides firmware files containing the scan tests via the webpage [#f1]_.
* Look under "In-Field Scan Test Images Download" section towards the
* end of the page. Similar to microcode, there are separate files for each
* family-model-stepping. IFS Images are not applicable for some test types.
* Wherever applicable the sysfs directory would provide a "current_batch" file
* (see below) for loading the image.
*
* .. [#f1] https://intel.com/InFieldScan
*
* IFS Image Loading
* -----------------
*
* The driver loads the tests into memory reserved BIOS local to each CPU
* socket in a two step process using writes to MSRs to first load the
* SHA hashes for the test. Then the tests themselves. Status MSRs provide
* feedback on the success/failure of these steps.
*
* The test files are kept in a fixed location: /lib/firmware/intel/ifs_<n>/
* For e.g if there are 3 test files, they would be named in the following
* fashion:
* ff-mm-ss-01.scan
* ff-mm-ss-02.scan
* ff-mm-ss-03.scan
* (where ff refers to family, mm indicates model and ss indicates stepping)
*
* A different test file can be loaded by writing the numerical portion
* (e.g 1, 2 or 3 in the above scenario) into the curent_batch file.
* To load ff-mm-ss-02.scan, the following command can be used::
*
* # echo 2 > /sys/devices/virtual/misc/intel_ifs_<n>/current_batch
*
* The above file can also be read to know the currently loaded image.
*
* Running tests
* -------------
*
* Tests are run by the driver synchronizing execution of all threads on a
* core and then writing to the ACTIVATE_SCAN MSR on all threads. Instruction
* execution continues when:
*
* 1) All tests have completed.
* 2) Execution was interrupted.
* 3) A test detected a problem.
*
* Note that ALL THREADS ON THE CORE ARE EFFECTIVELY OFFLINE FOR THE
* DURATION OF THE TEST. This can be up to 200 milliseconds. If the system
* is running latency sensitive applications that cannot tolerate an
* interruption of this magnitude, the system administrator must arrange
* to migrate those applications to other cores before running a core test.
* It may also be necessary to redirect interrupts to other CPUs.
*
* In all cases reading the corresponding test's STATUS MSR provides details on what
* happened. The driver makes the value of this MSR visible to applications
* via the "details" file (see below). Interrupted tests may be restarted.
*
* The IFS driver provides sysfs interfaces via /sys/devices/virtual/misc/intel_ifs_<n>/
* to control execution:
*
* Test a specific core::
*
* # echo <cpu#> > /sys/devices/virtual/misc/intel_ifs_<n>/run_test
*
* when HT is enabled any of the sibling cpu# can be specified to test
* its corresponding physical core. Since the tests are per physical core,
* the result of testing any thread is same. All siblings must be online
* to run a core test. It is only necessary to test one thread.
*
* For e.g. to test core corresponding to cpu5
*
* # echo 5 > /sys/devices/virtual/misc/intel_ifs_<n>/run_test
*
* Results of the last test is provided in /sys::
*
* $ cat /sys/devices/virtual/misc/intel_ifs_<n>/status
* pass
*
* Status can be one of pass, fail, untested
*
* Additional details of the last test is provided by the details file::
*
* $ cat /sys/devices/virtual/misc/intel_ifs_<n>/details
* 0x8081
*
* The details file reports the hex value of the test specific status MSR.
* Hardware defined error codes are documented in volume 4 of the Intel
* Software Developer's Manual but the error_code field may contain one of
* the following driver defined software codes:
*
* +------+--------------------+
* | 0xFD | Software timeout |
* +------+--------------------+
* | 0xFE | Partial completion |
* +------+--------------------+
*
* Driver design choices
* ---------------------
*
* 1) The ACTIVATE_SCAN MSR allows for running any consecutive subrange of
* available tests. But the driver always tries to run all tests and only
* uses the subrange feature to restart an interrupted test.
*
* 2) Hardware allows for some number of cores to be tested in parallel.
* The driver does not make use of this, it only tests one core at a time.
*
* Structural Based Functional Test at Field (SBAF):
* -------------------------------------------------
*
* SBAF is a new type of testing that provides comprehensive core test
* coverage complementing Scan at Field (SAF) testing. SBAF mimics the
* manufacturing screening environment and leverages the same test suite.
* It makes use of Design For Test (DFT) observation sites and features
* to maximize coverage in minimum time.
*
* Similar to the SAF test, SBAF isolates the core under test from the
* rest of the system during execution. Upon completion, the core
* seamlessly resets to its pre-test state and resumes normal operation.
* Any machine checks or hangs encountered during the test are confined to
* the isolated core, preventing disruption to the overall system.
*
* Like the SAF test, the SBAF test is also divided into multiple batches,
* and each batch test can take hundreds of milliseconds (100-200 ms) to
* complete. If such a lengthy interruption is undesirable, it is
* recommended to relocate the time-sensitive applications to other cores.
*/
#include <linux/device.h>
#include <linux/miscdevice.h>
#define MSR_ARRAY_BIST 0x00000105
#define MSR_COPY_SBAF_HASHES 0x000002b8
#define MSR_SBAF_HASHES_STATUS 0x000002b9
#define MSR_AUTHENTICATE_AND_COPY_SBAF_CHUNK 0x000002ba
#define MSR_SBAF_CHUNKS_AUTHENTICATION_STATUS 0x000002bb
#define MSR_ACTIVATE_SBAF 0x000002bc
#define MSR_SBAF_STATUS 0x000002bd
#define MSR_COPY_SCAN_HASHES 0x000002c2
#define MSR_SCAN_HASHES_STATUS 0x000002c3
#define MSR_AUTHENTICATE_AND_COPY_CHUNK 0x000002c4
#define MSR_CHUNKS_AUTHENTICATION_STATUS 0x000002c5
#define MSR_ACTIVATE_SCAN 0x000002c6
#define MSR_SCAN_STATUS 0x000002c7
#define MSR_ARRAY_TRIGGER 0x000002d6
#define MSR_ARRAY_STATUS 0x000002d7
#define MSR_SAF_CTRL 0x000004f0
#define MSR_SBAF_CTRL 0x000004f8
#define SCAN_NOT_TESTED 0
#define SCAN_TEST_PASS 1
#define SCAN_TEST_FAIL 2
#define IFS_TYPE_SAF 0
#define IFS_TYPE_ARRAY_BIST 1
#define IFS_TYPE_SBAF 2
#define ARRAY_GEN0 0
#define ARRAY_GEN1 1
/* MSR_SCAN_HASHES_STATUS bit fields */
union ifs_scan_hashes_status {
u64 data;
struct {
u32 chunk_size :16;
u32 num_chunks :8;
u32 rsvd1 :8;
u32 error_code :8;
u32 rsvd2 :11;
u32 max_core_limit :12;
u32 valid :1;
};
};
union ifs_scan_hashes_status_gen2 {
u64 data;
struct {
u16 chunk_size;
u16 num_chunks;
u32 error_code :8;
u32 chunks_in_stride :9;
u32 rsvd :2;
u32 max_core_limit :12;
u32 valid :1;
};
};
/* MSR_CHUNKS_AUTH_STATUS bit fields */
union ifs_chunks_auth_status {
u64 data;
struct {
u32 valid_chunks :8;
u32 total_chunks :8;
u32 rsvd1 :16;
u32 error_code :8;
u32 rsvd2 :24;
};
};
union ifs_chunks_auth_status_gen2 {
u64 data;
struct {
u16 valid_chunks;
u16 total_chunks;
u32 error_code :8;
u32 rsvd2 :8;
u32 max_bundle :16;
};
};
/* MSR_ACTIVATE_SCAN bit fields */
union ifs_scan {
u64 data;
struct {
union {
struct {
u8 start;
u8 stop;
u16 rsvd;
} gen0;
struct {
u16 start;
u16 stop;
} gen2;
};
u32 delay :31;
u32 sigmce :1;
};
};
/* MSR_SCAN_STATUS bit fields */
union ifs_status {
u64 data;
struct {
union {
struct {
u8 chunk_num;
u8 chunk_stop_index;
u16 rsvd1;
} gen0;
struct {
u16 chunk_num;
u16 chunk_stop_index;
} gen2;
};
u32 error_code :8;
u32 rsvd2 :22;
u32 control_error :1;
u32 signature_error :1;
};
};
/* MSR_ARRAY_BIST bit fields */
union ifs_array {
u64 data;
struct {
u32 array_bitmask;
u16 array_bank;
u16 rsvd :15;
u16 ctrl_result :1;
};
};
/* MSR_ACTIVATE_SBAF bit fields */
union ifs_sbaf {
u64 data;
struct {
u32 bundle_idx :9;
u32 rsvd1 :5;
u32 pgm_idx :2;
u32 rsvd2 :16;
u32 delay :31;
u32 sigmce :1;
};
};
/* MSR_SBAF_STATUS bit fields */
union ifs_sbaf_status {
u64 data;
struct {
u32 bundle_idx :9;
u32 rsvd1 :5;
u32 pgm_idx :2;
u32 rsvd2 :16;
u32 error_code :8;
u32 rsvd3 :21;
u32 test_fail :1;
u32 sbaf_status :2;
};
};
/*
* Driver populated error-codes
* 0xFD: Test timed out before completing all the chunks.
* 0xFE: not all scan chunks were executed. Maximum forward progress retries exceeded.
*/
#define IFS_SW_TIMEOUT 0xFD
#define IFS_SW_PARTIAL_COMPLETION 0xFE
#define IFS_SUFFIX_SZ 5
struct ifs_test_caps {
int integrity_cap_bit;
int test_num;
char image_suffix[IFS_SUFFIX_SZ];
};
/**
* struct ifs_test_msrs - MSRs used in IFS tests
* @copy_hashes: Copy test hash data
* @copy_hashes_status: Status of copied test hash data
* @copy_chunks: Copy chunks of the test data
* @copy_chunks_status: Status of the copied test data chunks
* @test_ctrl: Control the test attributes
*/
struct ifs_test_msrs {
u32 copy_hashes;
u32 copy_hashes_status;
u32 copy_chunks;
u32 copy_chunks_status;
u32 test_ctrl;
};
/**
* struct ifs_data - attributes related to intel IFS driver
* @loaded_version: stores the currently loaded ifs image version.
* @loaded: If a valid test binary has been loaded into the memory
* @loading_error: Error occurred on another CPU while loading image
* @valid_chunks: number of chunks which could be validated.
* @status: it holds simple status pass/fail/untested
* @scan_details: opaque scan status code from h/w
* @cur_batch: number indicating the currently loaded test file
* @generation: IFS test generation enumerated by hardware
* @chunk_size: size of a test chunk
* @array_gen: test generation of array test
* @max_bundle: maximum bundle index
*/
struct ifs_data {
int loaded_version;
bool loaded;
bool loading_error;
int valid_chunks;
int status;
u64 scan_details;
u32 cur_batch;
u32 generation;
u32 chunk_size;
u32 array_gen;
u32 max_bundle;
};
struct ifs_work {
struct work_struct w;
struct device *dev;
};
struct ifs_device {
const struct ifs_test_caps *test_caps;
const struct ifs_test_msrs *test_msrs;
struct ifs_data rw_data;
struct miscdevice misc;
};
static inline struct ifs_data *ifs_get_data(struct device *dev)
{
struct miscdevice *m = dev_get_drvdata(dev);
struct ifs_device *d = container_of(m, struct ifs_device, misc);
return &d->rw_data;
}
static inline const struct ifs_test_caps *ifs_get_test_caps(struct device *dev)
{
struct miscdevice *m = dev_get_drvdata(dev);
struct ifs_device *d = container_of(m, struct ifs_device, misc);
return d->test_caps;
}
static inline const struct ifs_test_msrs *ifs_get_test_msrs(struct device *dev)
{
struct miscdevice *m = dev_get_drvdata(dev);
struct ifs_device *d = container_of(m, struct ifs_device, misc);
return d->test_msrs;
}
extern bool *ifs_pkg_auth;
int ifs_load_firmware(struct device *dev);
int do_core_test(int cpu, struct device *dev);
extern struct attribute *plat_ifs_attrs[];
extern struct attribute *plat_ifs_array_attrs[];
#endif
@@ -0,0 +1,438 @@
// 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 <asm/msr.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 */
wrmsrq(msrs->copy_hashes, ifs_hash_ptr);
rdmsrq(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;
wrmsrq(msrs->copy_chunks, linear_addr);
rdmsrq(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)) {
wrmsrq(msrs->copy_hashes, ifs_hash_ptr);
rdmsrq(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) {
wrmsrq(msrs->test_ctrl, INVALIDATE_STRIDE);
rdmsrq(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();
wrmsrq(msrs->copy_chunks, (u64)chunk_table);
local_irq_enable();
rdmsrq(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;
}
@@ -0,0 +1,663 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2022 Intel Corporation. */
#include <linux/cpu.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/nmi.h>
#include <linux/slab.h>
#include <linux/stop_machine.h>
#include <asm/msr.h>
#include "ifs.h"
/*
* Note all code and data in this file is protected by
* ifs_sem. On HT systems all threads on a core will
* execute together, but only the first thread on the
* core will update results of the test.
*/
#define CREATE_TRACE_POINTS
#include <trace/events/intel_ifs.h>
/* Max retries on the same chunk */
#define MAX_IFS_RETRIES 5
struct run_params {
struct ifs_data *ifsd;
union ifs_scan *activate;
union ifs_status status;
};
struct sbaf_run_params {
struct ifs_data *ifsd;
int *retry_cnt;
union ifs_sbaf *activate;
union ifs_sbaf_status status;
};
/*
* Number of TSC cycles that a logical CPU will wait for the other
* logical CPU on the core in the WRMSR(ACTIVATE_SCAN).
*/
#define IFS_THREAD_WAIT 100000
enum ifs_status_err_code {
IFS_NO_ERROR = 0,
IFS_OTHER_THREAD_COULD_NOT_JOIN = 1,
IFS_INTERRUPTED_BEFORE_RENDEZVOUS = 2,
IFS_POWER_MGMT_INADEQUATE_FOR_SCAN = 3,
IFS_INVALID_CHUNK_RANGE = 4,
IFS_MISMATCH_ARGUMENTS_BETWEEN_THREADS = 5,
IFS_CORE_NOT_CAPABLE_CURRENTLY = 6,
IFS_UNASSIGNED_ERROR_CODE = 7,
IFS_EXCEED_NUMBER_OF_THREADS_CONCURRENT = 8,
IFS_INTERRUPTED_DURING_EXECUTION = 9,
IFS_UNASSIGNED_ERROR_CODE_0xA = 0xA,
IFS_CORRUPTED_CHUNK = 0xB,
};
static const char * const scan_test_status[] = {
[IFS_NO_ERROR] = "SCAN no error",
[IFS_OTHER_THREAD_COULD_NOT_JOIN] = "Other thread could not join.",
[IFS_INTERRUPTED_BEFORE_RENDEZVOUS] = "Interrupt occurred prior to SCAN coordination.",
[IFS_POWER_MGMT_INADEQUATE_FOR_SCAN] =
"Core Abort SCAN Response due to power management condition.",
[IFS_INVALID_CHUNK_RANGE] = "Non valid chunks in the range",
[IFS_MISMATCH_ARGUMENTS_BETWEEN_THREADS] = "Mismatch in arguments between threads T0/T1.",
[IFS_CORE_NOT_CAPABLE_CURRENTLY] = "Core not capable of performing SCAN currently",
[IFS_UNASSIGNED_ERROR_CODE] = "Unassigned error code 0x7",
[IFS_EXCEED_NUMBER_OF_THREADS_CONCURRENT] =
"Exceeded number of Logical Processors (LP) allowed to run Scan-At-Field concurrently",
[IFS_INTERRUPTED_DURING_EXECUTION] = "Interrupt occurred prior to SCAN start",
[IFS_UNASSIGNED_ERROR_CODE_0xA] = "Unassigned error code 0xA",
[IFS_CORRUPTED_CHUNK] = "Scan operation aborted due to corrupted image. Try reloading",
};
static void message_not_tested(struct device *dev, int cpu, union ifs_status status)
{
struct ifs_data *ifsd = ifs_get_data(dev);
/*
* control_error is set when the microcode runs into a problem
* loading the image from the reserved BIOS memory, or it has
* been corrupted. Reloading the image may fix this issue.
*/
if (status.control_error) {
dev_warn(dev, "CPU(s) %*pbl: Scan controller error. Batch: %02x version: 0x%x\n",
cpumask_pr_args(cpu_smt_mask(cpu)), ifsd->cur_batch, ifsd->loaded_version);
return;
}
if (status.error_code < ARRAY_SIZE(scan_test_status)) {
dev_info(dev, "CPU(s) %*pbl: SCAN operation did not start. %s\n",
cpumask_pr_args(cpu_smt_mask(cpu)),
scan_test_status[status.error_code]);
} else if (status.error_code == IFS_SW_TIMEOUT) {
dev_info(dev, "CPU(s) %*pbl: software timeout during scan\n",
cpumask_pr_args(cpu_smt_mask(cpu)));
} else if (status.error_code == IFS_SW_PARTIAL_COMPLETION) {
dev_info(dev, "CPU(s) %*pbl: %s\n",
cpumask_pr_args(cpu_smt_mask(cpu)),
"Not all scan chunks were executed. Maximum forward progress retries exceeded");
} else {
dev_info(dev, "CPU(s) %*pbl: SCAN unknown status %llx\n",
cpumask_pr_args(cpu_smt_mask(cpu)), status.data);
}
}
static void message_fail(struct device *dev, int cpu, union ifs_status status)
{
struct ifs_data *ifsd = ifs_get_data(dev);
/*
* signature_error is set when the output from the scan chains does not
* match the expected signature. This might be a transient problem (e.g.
* due to a bit flip from an alpha particle or neutron). If the problem
* repeats on a subsequent test, then it indicates an actual problem in
* the core being tested.
*/
if (status.signature_error) {
dev_err(dev, "CPU(s) %*pbl: test signature incorrect. Batch: %02x version: 0x%x\n",
cpumask_pr_args(cpu_smt_mask(cpu)), ifsd->cur_batch, ifsd->loaded_version);
}
}
static bool can_restart(union ifs_status status)
{
enum ifs_status_err_code err_code = status.error_code;
/* Signature for chunk is bad, or scan test failed */
if (status.signature_error || status.control_error)
return false;
switch (err_code) {
case IFS_NO_ERROR:
case IFS_OTHER_THREAD_COULD_NOT_JOIN:
case IFS_INTERRUPTED_BEFORE_RENDEZVOUS:
case IFS_POWER_MGMT_INADEQUATE_FOR_SCAN:
case IFS_EXCEED_NUMBER_OF_THREADS_CONCURRENT:
case IFS_INTERRUPTED_DURING_EXECUTION:
return true;
case IFS_INVALID_CHUNK_RANGE:
case IFS_MISMATCH_ARGUMENTS_BETWEEN_THREADS:
case IFS_CORE_NOT_CAPABLE_CURRENTLY:
case IFS_UNASSIGNED_ERROR_CODE:
case IFS_UNASSIGNED_ERROR_CODE_0xA:
case IFS_CORRUPTED_CHUNK:
break;
}
return false;
}
#define SPINUNIT 100 /* 100 nsec */
static atomic_t array_cpus_in;
static atomic_t scan_cpus_in;
static atomic_t sbaf_cpus_in;
/*
* Simplified cpu sibling rendezvous loop based on microcode loader __wait_for_cpus()
*/
static void wait_for_sibling_cpu(atomic_t *t, long long timeout)
{
int cpu = smp_processor_id();
const struct cpumask *smt_mask = cpu_smt_mask(cpu);
int all_cpus = cpumask_weight(smt_mask);
atomic_inc(t);
while (atomic_read(t) < all_cpus) {
if (timeout < SPINUNIT)
return;
ndelay(SPINUNIT);
timeout -= SPINUNIT;
touch_nmi_watchdog();
}
}
/*
* Execute the scan. Called "simultaneously" on all threads of a core
* at high priority using the stop_cpus mechanism.
*/
static int doscan(void *data)
{
int cpu = smp_processor_id(), start, stop;
struct run_params *params = data;
union ifs_status status;
struct ifs_data *ifsd;
int first;
ifsd = params->ifsd;
if (ifsd->generation) {
start = params->activate->gen2.start;
stop = params->activate->gen2.stop;
} else {
start = params->activate->gen0.start;
stop = params->activate->gen0.stop;
}
/* Only the first logical CPU on a core reports result */
first = cpumask_first(cpu_smt_mask(cpu));
wait_for_sibling_cpu(&scan_cpus_in, NSEC_PER_SEC);
/*
* This WRMSR will wait for other HT threads to also write
* to this MSR (at most for activate.delay cycles). Then it
* starts scan of each requested chunk. The core scan happens
* during the "execution" of the WRMSR. This instruction can
* take up to 200 milliseconds (in the case where all chunks
* are processed in a single pass) before it retires.
*/
wrmsrq(MSR_ACTIVATE_SCAN, params->activate->data);
rdmsrq(MSR_SCAN_STATUS, status.data);
trace_ifs_status(ifsd->cur_batch, start, stop, status.data);
/* Pass back the result of the scan */
if (cpu == first)
params->status = status;
return 0;
}
/*
* Use stop_core_cpuslocked() to synchronize writing to MSR_ACTIVATE_SCAN
* on all threads of the core to be tested. Loop if necessary to complete
* run of all chunks. Include some defensive tests to make sure forward
* progress is made, and that the whole test completes in a reasonable time.
*/
static void ifs_test_core(int cpu, struct device *dev)
{
union ifs_status status = {};
union ifs_scan activate;
unsigned long timeout;
struct ifs_data *ifsd;
int to_start, to_stop;
int status_chunk;
struct run_params params;
int retries;
ifsd = ifs_get_data(dev);
activate.gen0.rsvd = 0;
activate.delay = IFS_THREAD_WAIT;
activate.sigmce = 0;
to_start = 0;
to_stop = ifsd->valid_chunks - 1;
params.ifsd = ifs_get_data(dev);
if (ifsd->generation) {
activate.gen2.start = to_start;
activate.gen2.stop = to_stop;
} else {
activate.gen0.start = to_start;
activate.gen0.stop = to_stop;
}
timeout = jiffies + HZ / 2;
retries = MAX_IFS_RETRIES;
while (to_start <= to_stop) {
if (time_after(jiffies, timeout)) {
status.error_code = IFS_SW_TIMEOUT;
break;
}
params.activate = &activate;
atomic_set(&scan_cpus_in, 0);
stop_core_cpuslocked(cpu, doscan, &params);
status = params.status;
/* Some cases can be retried, give up for others */
if (!can_restart(status))
break;
status_chunk = ifsd->generation ? status.gen2.chunk_num : status.gen0.chunk_num;
if (status_chunk == to_start) {
/* Check for forward progress */
if (--retries == 0) {
if (status.error_code == IFS_NO_ERROR)
status.error_code = IFS_SW_PARTIAL_COMPLETION;
break;
}
} else {
retries = MAX_IFS_RETRIES;
if (ifsd->generation)
activate.gen2.start = status_chunk;
else
activate.gen0.start = status_chunk;
to_start = status_chunk;
}
}
/* Update status for this core */
ifsd->scan_details = status.data;
if (status.signature_error) {
ifsd->status = SCAN_TEST_FAIL;
message_fail(dev, cpu, status);
} else if (status.control_error || status.error_code) {
ifsd->status = SCAN_NOT_TESTED;
message_not_tested(dev, cpu, status);
} else {
ifsd->status = SCAN_TEST_PASS;
}
}
static int do_array_test(void *data)
{
union ifs_array *command = data;
int cpu = smp_processor_id();
int first;
wait_for_sibling_cpu(&array_cpus_in, NSEC_PER_SEC);
/*
* Only one logical CPU on a core needs to trigger the Array test via MSR write.
*/
first = cpumask_first(cpu_smt_mask(cpu));
if (cpu == first) {
wrmsrq(MSR_ARRAY_BIST, command->data);
/* Pass back the result of the test */
rdmsrq(MSR_ARRAY_BIST, command->data);
}
return 0;
}
static void ifs_array_test_core(int cpu, struct device *dev)
{
union ifs_array command = {};
bool timed_out = false;
struct ifs_data *ifsd;
unsigned long timeout;
ifsd = ifs_get_data(dev);
command.array_bitmask = ~0U;
timeout = jiffies + HZ / 2;
do {
if (time_after(jiffies, timeout)) {
timed_out = true;
break;
}
atomic_set(&array_cpus_in, 0);
stop_core_cpuslocked(cpu, do_array_test, &command);
if (command.ctrl_result)
break;
} while (command.array_bitmask);
ifsd->scan_details = command.data;
if (command.ctrl_result)
ifsd->status = SCAN_TEST_FAIL;
else if (timed_out || command.array_bitmask)
ifsd->status = SCAN_NOT_TESTED;
else
ifsd->status = SCAN_TEST_PASS;
}
#define ARRAY_GEN1_TEST_ALL_ARRAYS 0x0ULL
#define ARRAY_GEN1_STATUS_FAIL 0x1ULL
static int do_array_test_gen1(void *status)
{
int cpu = smp_processor_id();
int first;
first = cpumask_first(cpu_smt_mask(cpu));
if (cpu == first) {
wrmsrq(MSR_ARRAY_TRIGGER, ARRAY_GEN1_TEST_ALL_ARRAYS);
rdmsrq(MSR_ARRAY_STATUS, *((u64 *)status));
}
return 0;
}
static void ifs_array_test_gen1(int cpu, struct device *dev)
{
struct ifs_data *ifsd = ifs_get_data(dev);
u64 status = 0;
stop_core_cpuslocked(cpu, do_array_test_gen1, &status);
ifsd->scan_details = status;
if (status & ARRAY_GEN1_STATUS_FAIL)
ifsd->status = SCAN_TEST_FAIL;
else
ifsd->status = SCAN_TEST_PASS;
}
#define SBAF_STATUS_PASS 0
#define SBAF_STATUS_SIGN_FAIL 1
#define SBAF_STATUS_INTR 2
#define SBAF_STATUS_TEST_FAIL 3
enum sbaf_status_err_code {
IFS_SBAF_NO_ERROR = 0,
IFS_SBAF_OTHER_THREAD_COULD_NOT_JOIN = 1,
IFS_SBAF_INTERRUPTED_BEFORE_RENDEZVOUS = 2,
IFS_SBAF_UNASSIGNED_ERROR_CODE3 = 3,
IFS_SBAF_INVALID_BUNDLE_INDEX = 4,
IFS_SBAF_MISMATCH_ARGS_BETWEEN_THREADS = 5,
IFS_SBAF_CORE_NOT_CAPABLE_CURRENTLY = 6,
IFS_SBAF_UNASSIGNED_ERROR_CODE7 = 7,
IFS_SBAF_EXCEED_NUMBER_OF_THREADS_CONCURRENT = 8,
IFS_SBAF_INTERRUPTED_DURING_EXECUTION = 9,
IFS_SBAF_INVALID_PROGRAM_INDEX = 0xA,
IFS_SBAF_CORRUPTED_CHUNK = 0xB,
IFS_SBAF_DID_NOT_START = 0xC,
};
static const char * const sbaf_test_status[] = {
[IFS_SBAF_NO_ERROR] = "SBAF no error",
[IFS_SBAF_OTHER_THREAD_COULD_NOT_JOIN] = "Other thread could not join.",
[IFS_SBAF_INTERRUPTED_BEFORE_RENDEZVOUS] = "Interrupt occurred prior to SBAF coordination.",
[IFS_SBAF_UNASSIGNED_ERROR_CODE3] = "Unassigned error code 0x3",
[IFS_SBAF_INVALID_BUNDLE_INDEX] = "Non-valid sbaf bundles. Reload test image",
[IFS_SBAF_MISMATCH_ARGS_BETWEEN_THREADS] = "Mismatch in arguments between threads T0/T1.",
[IFS_SBAF_CORE_NOT_CAPABLE_CURRENTLY] = "Core not capable of performing SBAF currently",
[IFS_SBAF_UNASSIGNED_ERROR_CODE7] = "Unassigned error code 0x7",
[IFS_SBAF_EXCEED_NUMBER_OF_THREADS_CONCURRENT] = "Exceeded number of Logical Processors (LP) allowed to run Scan-At-Field concurrently",
[IFS_SBAF_INTERRUPTED_DURING_EXECUTION] = "Interrupt occurred prior to SBAF start",
[IFS_SBAF_INVALID_PROGRAM_INDEX] = "SBAF program index not valid",
[IFS_SBAF_CORRUPTED_CHUNK] = "SBAF operation aborted due to corrupted chunk",
[IFS_SBAF_DID_NOT_START] = "SBAF operation did not start",
};
static void sbaf_message_not_tested(struct device *dev, int cpu, u64 status_data)
{
union ifs_sbaf_status status = (union ifs_sbaf_status)status_data;
if (status.error_code < ARRAY_SIZE(sbaf_test_status)) {
dev_info(dev, "CPU(s) %*pbl: SBAF operation did not start. %s\n",
cpumask_pr_args(cpu_smt_mask(cpu)),
sbaf_test_status[status.error_code]);
} else if (status.error_code == IFS_SW_TIMEOUT) {
dev_info(dev, "CPU(s) %*pbl: software timeout during scan\n",
cpumask_pr_args(cpu_smt_mask(cpu)));
} else if (status.error_code == IFS_SW_PARTIAL_COMPLETION) {
dev_info(dev, "CPU(s) %*pbl: %s\n",
cpumask_pr_args(cpu_smt_mask(cpu)),
"Not all SBAF bundles executed. Maximum forward progress retries exceeded");
} else {
dev_info(dev, "CPU(s) %*pbl: SBAF unknown status %llx\n",
cpumask_pr_args(cpu_smt_mask(cpu)), status.data);
}
}
static void sbaf_message_fail(struct device *dev, int cpu, union ifs_sbaf_status status)
{
/* Failed signature check is set when SBAF signature did not match the expected value */
if (status.sbaf_status == SBAF_STATUS_SIGN_FAIL) {
dev_err(dev, "CPU(s) %*pbl: Failed signature check\n",
cpumask_pr_args(cpu_smt_mask(cpu)));
}
/* Failed to reach end of test */
if (status.sbaf_status == SBAF_STATUS_TEST_FAIL) {
dev_err(dev, "CPU(s) %*pbl: Failed to complete test\n",
cpumask_pr_args(cpu_smt_mask(cpu)));
}
}
static bool sbaf_bundle_completed(union ifs_sbaf_status status)
{
return !(status.sbaf_status || status.error_code);
}
static bool sbaf_can_restart(union ifs_sbaf_status status)
{
enum sbaf_status_err_code err_code = status.error_code;
/* Signature for chunk is bad, or scan test failed */
if (status.sbaf_status == SBAF_STATUS_SIGN_FAIL ||
status.sbaf_status == SBAF_STATUS_TEST_FAIL)
return false;
switch (err_code) {
case IFS_SBAF_NO_ERROR:
case IFS_SBAF_OTHER_THREAD_COULD_NOT_JOIN:
case IFS_SBAF_INTERRUPTED_BEFORE_RENDEZVOUS:
case IFS_SBAF_EXCEED_NUMBER_OF_THREADS_CONCURRENT:
case IFS_SBAF_INTERRUPTED_DURING_EXECUTION:
return true;
case IFS_SBAF_UNASSIGNED_ERROR_CODE3:
case IFS_SBAF_INVALID_BUNDLE_INDEX:
case IFS_SBAF_MISMATCH_ARGS_BETWEEN_THREADS:
case IFS_SBAF_CORE_NOT_CAPABLE_CURRENTLY:
case IFS_SBAF_UNASSIGNED_ERROR_CODE7:
case IFS_SBAF_INVALID_PROGRAM_INDEX:
case IFS_SBAF_CORRUPTED_CHUNK:
case IFS_SBAF_DID_NOT_START:
break;
}
return false;
}
/*
* Execute the SBAF test. Called "simultaneously" on all threads of a core
* at high priority using the stop_cpus mechanism.
*/
static int dosbaf(void *data)
{
struct sbaf_run_params *run_params = data;
int cpu = smp_processor_id();
union ifs_sbaf_status status;
struct ifs_data *ifsd;
int first;
ifsd = run_params->ifsd;
/* Only the first logical CPU on a core reports result */
first = cpumask_first(cpu_smt_mask(cpu));
wait_for_sibling_cpu(&sbaf_cpus_in, NSEC_PER_SEC);
/*
* This WRMSR will wait for other HT threads to also write
* to this MSR (at most for activate.delay cycles). Then it
* starts scan of each requested bundle. The core test happens
* during the "execution" of the WRMSR.
*/
wrmsrq(MSR_ACTIVATE_SBAF, run_params->activate->data);
rdmsrq(MSR_SBAF_STATUS, status.data);
trace_ifs_sbaf(ifsd->cur_batch, *run_params->activate, status);
/* Pass back the result of the test */
if (cpu == first)
run_params->status = status;
return 0;
}
static void ifs_sbaf_test_core(int cpu, struct device *dev)
{
struct sbaf_run_params run_params;
union ifs_sbaf_status status = {};
union ifs_sbaf activate;
unsigned long timeout;
struct ifs_data *ifsd;
int stop_bundle;
int retries;
ifsd = ifs_get_data(dev);
activate.data = 0;
activate.delay = IFS_THREAD_WAIT;
timeout = jiffies + 2 * HZ;
retries = MAX_IFS_RETRIES;
activate.bundle_idx = 0;
stop_bundle = ifsd->max_bundle;
while (activate.bundle_idx <= stop_bundle) {
if (time_after(jiffies, timeout)) {
status.error_code = IFS_SW_TIMEOUT;
break;
}
atomic_set(&sbaf_cpus_in, 0);
run_params.ifsd = ifsd;
run_params.activate = &activate;
run_params.retry_cnt = &retries;
stop_core_cpuslocked(cpu, dosbaf, &run_params);
status = run_params.status;
if (sbaf_bundle_completed(status)) {
activate.bundle_idx = status.bundle_idx + 1;
activate.pgm_idx = 0;
retries = MAX_IFS_RETRIES;
continue;
}
/* Some cases can be retried, give up for others */
if (!sbaf_can_restart(status))
break;
if (status.pgm_idx == activate.pgm_idx) {
/* If no progress retry */
if (--retries == 0) {
if (status.error_code == IFS_NO_ERROR)
status.error_code = IFS_SW_PARTIAL_COMPLETION;
break;
}
} else {
/* if some progress, more pgms remaining in bundle, reset retries */
retries = MAX_IFS_RETRIES;
activate.bundle_idx = status.bundle_idx;
activate.pgm_idx = status.pgm_idx;
}
}
/* Update status for this core */
ifsd->scan_details = status.data;
if (status.sbaf_status == SBAF_STATUS_SIGN_FAIL ||
status.sbaf_status == SBAF_STATUS_TEST_FAIL) {
ifsd->status = SCAN_TEST_FAIL;
sbaf_message_fail(dev, cpu, status);
} else if (status.error_code || status.sbaf_status == SBAF_STATUS_INTR ||
(activate.bundle_idx < stop_bundle)) {
ifsd->status = SCAN_NOT_TESTED;
sbaf_message_not_tested(dev, cpu, status.data);
} else {
ifsd->status = SCAN_TEST_PASS;
}
}
/*
* Initiate per core test. It wakes up work queue threads on the target cpu and
* its sibling cpu. Once all sibling threads wake up, the scan test gets executed and
* wait for all sibling threads to finish the scan test.
*/
int do_core_test(int cpu, struct device *dev)
{
const struct ifs_test_caps *test = ifs_get_test_caps(dev);
struct ifs_data *ifsd = ifs_get_data(dev);
int ret = 0;
/* Prevent CPUs from being taken offline during the scan test */
cpus_read_lock();
if (!cpu_online(cpu)) {
dev_info(dev, "cannot test on the offline cpu %d\n", cpu);
ret = -EINVAL;
goto out;
}
switch (test->test_num) {
case IFS_TYPE_SAF:
if (!ifsd->loaded)
ret = -EPERM;
else
ifs_test_core(cpu, dev);
break;
case IFS_TYPE_ARRAY_BIST:
if (ifsd->array_gen == ARRAY_GEN0)
ifs_array_test_core(cpu, dev);
else
ifs_array_test_gen1(cpu, dev);
break;
case IFS_TYPE_SBAF:
if (!ifsd->loaded)
ret = -EPERM;
else
ifs_sbaf_test_core(cpu, dev);
break;
default:
ret = -EINVAL;
}
out:
cpus_read_unlock();
return ret;
}
@@ -0,0 +1,155 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2022 Intel Corporation. */
#include <linux/cpu.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
#include "ifs.h"
/*
* Protects against simultaneous tests on multiple cores, or
* reloading can file while a test is in progress
*/
static DEFINE_SEMAPHORE(ifs_sem, 1);
/*
* The sysfs interface to check additional details of last test
* cat /sys/devices/system/platform/ifs/details
*/
static ssize_t details_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct ifs_data *ifsd = ifs_get_data(dev);
return sysfs_emit(buf, "%#llx\n", ifsd->scan_details);
}
static DEVICE_ATTR_RO(details);
static const char * const status_msg[] = {
[SCAN_NOT_TESTED] = "untested",
[SCAN_TEST_PASS] = "pass",
[SCAN_TEST_FAIL] = "fail"
};
/*
* The sysfs interface to check the test status:
* To check the status of last test
* cat /sys/devices/platform/ifs/status
*/
static ssize_t status_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct ifs_data *ifsd = ifs_get_data(dev);
return sysfs_emit(buf, "%s\n", status_msg[ifsd->status]);
}
static DEVICE_ATTR_RO(status);
/*
* The sysfs interface for single core testing
* To start test, for example, cpu5
* echo 5 > /sys/devices/platform/ifs/run_test
* To check the result:
* cat /sys/devices/platform/ifs/result
* The sibling core gets tested at the same time.
*/
static ssize_t run_test_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int cpu;
int rc;
rc = kstrtouint(buf, 0, &cpu);
if (rc < 0 || cpu >= nr_cpu_ids)
return -EINVAL;
if (down_interruptible(&ifs_sem))
return -EINTR;
rc = do_core_test(cpu, dev);
up(&ifs_sem);
return rc ? rc : count;
}
static DEVICE_ATTR_WO(run_test);
static ssize_t current_batch_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ifs_data *ifsd = ifs_get_data(dev);
unsigned int cur_batch;
int rc;
rc = kstrtouint(buf, 0, &cur_batch);
if (rc < 0 || cur_batch > 0xff)
return -EINVAL;
if (down_interruptible(&ifs_sem))
return -EINTR;
ifsd->cur_batch = cur_batch;
rc = ifs_load_firmware(dev);
up(&ifs_sem);
return (rc == 0) ? count : rc;
}
static ssize_t current_batch_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ifs_data *ifsd = ifs_get_data(dev);
if (!ifsd->loaded)
return sysfs_emit(buf, "none\n");
else
return sysfs_emit(buf, "0x%02x\n", ifsd->cur_batch);
}
static DEVICE_ATTR_RW(current_batch);
/*
* Display currently loaded IFS image version.
*/
static ssize_t image_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ifs_data *ifsd = ifs_get_data(dev);
if (!ifsd->loaded)
return sysfs_emit(buf, "%s\n", "none");
else
return sysfs_emit(buf, "%#x\n", ifsd->loaded_version);
}
static DEVICE_ATTR_RO(image_version);
/* global scan sysfs attributes */
struct attribute *plat_ifs_attrs[] = {
&dev_attr_details.attr,
&dev_attr_status.attr,
&dev_attr_run_test.attr,
&dev_attr_current_batch.attr,
&dev_attr_image_version.attr,
NULL
};
/* global array sysfs attributes */
struct attribute *plat_ifs_array_attrs[] = {
&dev_attr_details.attr,
&dev_attr_status.attr,
&dev_attr_run_test.attr,
NULL
};