| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * CIO inject interface |
| * |
| * Copyright IBM Corp. 2021 |
| * Author(s): Vineeth Vijayan <vneethv@linux.ibm.com> |
| */ |
| |
| #define KMSG_COMPONENT "cio" |
| #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt |
| |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/mm.h> |
| #include <linux/debugfs.h> |
| #include <asm/chpid.h> |
| |
| #include "cio_inject.h" |
| #include "cio_debug.h" |
| |
| static DEFINE_SPINLOCK(crw_inject_lock); |
| DEFINE_STATIC_KEY_FALSE(cio_inject_enabled); |
| static struct crw *crw_inject_data; |
| |
| /** |
| * crw_inject : Initiate the artificial CRW inject |
| * @crw: The data which needs to be injected as new CRW. |
| * |
| * The CRW handler is called, which will use the provided artificial |
| * data instead of the CRW from the underlying hardware. |
| * |
| * Return: 0 on success |
| */ |
| static int crw_inject(struct crw *crw) |
| { |
| int rc = 0; |
| struct crw *copy; |
| unsigned long flags; |
| |
| copy = kmemdup(crw, sizeof(*crw), GFP_KERNEL); |
| if (!copy) |
| return -ENOMEM; |
| |
| spin_lock_irqsave(&crw_inject_lock, flags); |
| if (crw_inject_data) { |
| kfree(copy); |
| rc = -EBUSY; |
| } else { |
| crw_inject_data = copy; |
| } |
| spin_unlock_irqrestore(&crw_inject_lock, flags); |
| |
| if (!rc) |
| crw_handle_channel_report(); |
| |
| return rc; |
| } |
| |
| /** |
| * stcrw_get_injected: Copy the artificial CRW data to CRW struct. |
| * @crw: The target CRW pointer. |
| * |
| * Retrieve an injected CRW data. Return 0 on success, 1 if no |
| * injected-CRW is available. The function reproduces the return |
| * code of the actual STCRW function. |
| */ |
| int stcrw_get_injected(struct crw *crw) |
| { |
| int rc = 1; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&crw_inject_lock, flags); |
| if (crw_inject_data) { |
| memcpy(crw, crw_inject_data, sizeof(*crw)); |
| kfree(crw_inject_data); |
| crw_inject_data = NULL; |
| rc = 0; |
| } |
| spin_unlock_irqrestore(&crw_inject_lock, flags); |
| |
| return rc; |
| } |
| |
| /* The debugfs write handler for crw_inject nodes operation */ |
| static ssize_t crw_inject_write(struct file *file, const char __user *buf, |
| size_t lbuf, loff_t *ppos) |
| { |
| u32 slct, oflw, chn, rsc, anc, erc, rsid; |
| struct crw crw; |
| char *buffer; |
| int rc; |
| |
| if (!static_branch_likely(&cio_inject_enabled)) { |
| pr_warn("CIO inject is not enabled - ignoring CRW inject\n"); |
| return -EINVAL; |
| } |
| |
| buffer = vmemdup_user(buf, lbuf); |
| if (IS_ERR(buffer)) |
| return -ENOMEM; |
| |
| rc = sscanf(buffer, "%x %x %x %x %x %x %x", &slct, &oflw, &chn, &rsc, &anc, |
| &erc, &rsid); |
| |
| kvfree(buffer); |
| if (rc != 7) { |
| pr_warn("crw_inject: Invalid format (need <solicited> <overflow> <chaining> <rsc> <ancillary> <erc> <rsid>)\n"); |
| return -EINVAL; |
| } |
| |
| memset(&crw, 0, sizeof(crw)); |
| crw.slct = slct; |
| crw.oflw = oflw; |
| crw.chn = chn; |
| crw.rsc = rsc; |
| crw.anc = anc; |
| crw.erc = erc; |
| crw.rsid = rsid; |
| |
| rc = crw_inject(&crw); |
| if (rc) |
| return rc; |
| |
| return lbuf; |
| } |
| |
| /* Debugfs write handler for inject_enable node*/ |
| static ssize_t enable_inject_write(struct file *file, const char __user *buf, |
| size_t lbuf, loff_t *ppos) |
| { |
| unsigned long en = 0; |
| int rc; |
| |
| rc = kstrtoul_from_user(buf, lbuf, 10, &en); |
| if (rc) |
| return rc; |
| |
| switch (en) { |
| case 0: |
| static_branch_disable(&cio_inject_enabled); |
| break; |
| case 1: |
| static_branch_enable(&cio_inject_enabled); |
| break; |
| } |
| |
| return lbuf; |
| } |
| |
| static const struct file_operations crw_fops = { |
| .owner = THIS_MODULE, |
| .write = crw_inject_write, |
| }; |
| |
| static const struct file_operations cio_en_fops = { |
| .owner = THIS_MODULE, |
| .write = enable_inject_write, |
| }; |
| |
| static int __init cio_inject_init(void) |
| { |
| /* enable_inject node enables the static branching */ |
| debugfs_create_file("enable_inject", 0200, cio_debugfs_dir, |
| NULL, &cio_en_fops); |
| |
| debugfs_create_file("crw_inject", 0200, cio_debugfs_dir, |
| NULL, &crw_fops); |
| return 0; |
| } |
| |
| device_initcall(cio_inject_init); |