blob: eeeb2ce844824e798a4d6c9f4bab07b788d2f8a3 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* The helper functions for fault injection.
*
* Copyright (C) 2023 Google LLC
*/
#include <linux/debugfs.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <gcip/gcip-fault-injection.h>
#include <gcip/gcip-kci.h>
#include <gcip/gcip-pm.h>
static int gcip_fault_inject_send_locked(struct gcip_fault_inject *injection)
{
int i, ret, offset = 0;
char *buf;
lockdep_assert_held(&injection->lock);
if (injection->progress != GCIP_FAULT_INJECT_PROGRESS_PENDING)
return 0;
buf = kzalloc(FAULT_INJECT_BUF_SIZE, GFP_KERNEL);
if (buf) {
for (i = 0; i < GCIP_FAULT_INJECT_OPAQUE_SIZE; i++)
offset += scnprintf(buf + offset, FAULT_INJECT_BUF_SIZE - offset, " %u",
injection->opaque[i]);
dev_info(injection->dev, "Inserting fault:%s", buf);
}
kfree(buf);
injection->progress = GCIP_FAULT_INJECT_PROGRESS_INJECTED;
ret = injection->send_kci(injection);
if (!ret) {
injection->fw_support_status = GCIP_FAULT_INJECT_STATUS_SUPPORTED;
} else if (ret == GCIP_KCI_ERROR_UNIMPLEMENTED) {
injection->fw_support_status = GCIP_FAULT_INJECT_STATUS_UNSUPPORTED;
} else {
injection->fw_support_status = GCIP_FAULT_INJECT_STATUS_ERROR;
if (ret > 0) {
dev_warn(injection->dev,
"Fault injection KCI not accepted by the firmware: %d\n", ret);
} else {
/* Keep pending if the failure was in the driver side. */
injection->progress = GCIP_FAULT_INJECT_PROGRESS_PENDING;
dev_warn(injection->dev, "Failed to send the fault injection KCI: %d", ret);
}
}
return ret;
}
/**
* gcip_fault_injection_set() - Set the fault-injection values received from the DebugFS.
* @filp: The file pointer.
* @buff: The user buffer holding the data to be written.
* @count: The size of the requested data transfer.
* @offp: The file position the user is accessing.
*
* Return: A non-negative return value represents the number of bytes successfully read.
*/
static ssize_t gcip_fault_injection_set(struct file *filp, const char __user *buff, size_t count,
loff_t *offp)
{
struct gcip_fault_inject *injection = filp->f_inode->i_private;
bool mcu_ready = !gcip_pm_get_if_powered(injection->pm, false);
char *input = NULL;
int ret;
int i;
int start = 0;
uint32_t val;
int consume;
if (*offp || (count + 1 >= FAULT_INJECT_BUF_SIZE)) {
ret = -EINVAL;
goto out;
}
input = kzalloc(count + 1, GFP_KERNEL);
if (!input) {
ret = -ENOMEM;
goto out;
}
if (copy_from_user(input, buff, count)) {
ret = -EFAULT;
goto out;
}
mutex_lock(&injection->lock);
if (injection->progress == GCIP_FAULT_INJECT_PROGRESS_PENDING)
dev_warn(injection->dev, "Ignore pending fault injection.\n");
memset(injection->opaque, 0, sizeof(injection->opaque));
for (i = 0; i < ARRAY_SIZE(injection->opaque); i++) {
if (sscanf(input + start, "%u%n", &val, &consume) <= 0)
break;
start += consume;
injection->opaque[i] = val;
}
injection->progress = GCIP_FAULT_INJECT_PROGRESS_PENDING;
if (!mcu_ready)
dev_dbg(injection->dev, "MCU is not ready, pend sending fault injection");
else
gcip_fault_inject_send_locked(injection);
ret = count;
mutex_unlock(&injection->lock);
out:
kfree(input);
if (mcu_ready)
gcip_pm_put(injection->pm);
return ret;
}
/* Write the fault injection progress rate and content. */
static void write_injection_content(struct gcip_fault_inject *injection, char *output, loff_t *offp,
const size_t buf_size)
{
int i;
if (injection->progress == GCIP_FAULT_INJECT_PROGRESS_NONE) {
*offp += scnprintf(output + *offp, buf_size - *offp, "none\n");
return;
}
*offp += scnprintf(output + *offp, buf_size - *offp, "%s\n",
injection->progress == GCIP_FAULT_INJECT_PROGRESS_PENDING ? "pending" :
"injected");
for (i = 0; i < GCIP_FAULT_INJECT_OPAQUE_SIZE; i++)
*offp += scnprintf(output + *offp, buf_size - *offp, "%.*s%u", i, " ",
injection->opaque[i]);
*offp += scnprintf(output + *offp, buf_size - *offp, "\n");
}
/**
* gcip_fault_injection_get() - Get the fault-injection values.
* @filp: The file pointer.
* @buff: The empty buffer where the newly read data should be placed.
* @count: The size of the requested data transfer.
* @offp: The file position the user is accessing.
*
* Return: A non-negative return value represents the number of bytes successfully written.
*/
static ssize_t gcip_fault_injection_get(struct file *filp, char __user *buff, size_t count,
loff_t *offp)
{
struct gcip_fault_inject *injection = filp->f_inode->i_private;
const size_t buf_size = min_t(size_t, count, FAULT_INJECT_BUF_SIZE);
int ret;
char *output;
if (*offp)
return 0;
output = kzalloc(buf_size, GFP_KERNEL);
if (!output)
return -ENOMEM;
mutex_lock(&injection->lock);
switch (injection->fw_support_status) {
case GCIP_FAULT_INJECT_STATUS_UNKNOWN:
*offp += scnprintf(output + *offp, buf_size - *offp, "unknown\n");
write_injection_content(injection, output, offp, buf_size);
break;
case GCIP_FAULT_INJECT_STATUS_ERROR:
*offp += scnprintf(output + *offp, buf_size - *offp, "error\n");
break;
case GCIP_FAULT_INJECT_STATUS_UNSUPPORTED:
*offp += scnprintf(output + *offp, buf_size - *offp, "unsupported\n");
break;
default:
*offp += scnprintf(output + *offp, buf_size - *offp, "supported\n");
write_injection_content(injection, output, offp, buf_size);
}
mutex_unlock(&injection->lock);
if (copy_to_user(buff, output, *offp))
ret = -EFAULT;
else
ret = *offp;
kfree(output);
return ret;
}
static const struct file_operations fault_inject_fops = { .write = gcip_fault_injection_set,
.read = gcip_fault_injection_get };
struct gcip_fault_inject *gcip_fault_inject_create(const struct gcip_fault_inject_args *args)
{
struct gcip_fault_inject *injection =
devm_kzalloc(args->dev, sizeof(*injection), GFP_KERNEL);
if (!injection)
return ERR_PTR(-ENOMEM);
injection->dev = args->dev;
injection->pm = args->pm;
injection->send_kci = args->send_kci;
injection->kci_data = args->kci_data;
injection->progress = GCIP_FAULT_INJECT_PROGRESS_NONE;
injection->fw_support_status = GCIP_FAULT_INJECT_STATUS_UNKNOWN;
mutex_init(&injection->lock);
injection->d_entry = debugfs_create_file(DEBUGFS_FAULT_INJECTION, 0600, args->parent_dentry,
injection, &fault_inject_fops);
return injection;
}
void gcip_fault_inject_destroy(struct gcip_fault_inject *injection)
{
if (injection) {
debugfs_remove(injection->d_entry);
devm_kfree(injection->dev, injection);
}
}
int gcip_fault_inject_send(struct gcip_fault_inject *injection)
{
int ret;
if (!injection)
return -ENODEV;
mutex_lock(&injection->lock);
ret = gcip_fault_inject_send_locked(injection);
mutex_unlock(&injection->lock);
return ret;
}