root/arch/powerpc/platforms/pseries/papr-indices.c
// SPDX-License-Identifier: GPL-2.0-only

#define pr_fmt(fmt) "papr-indices: " fmt

#include <linux/build_bug.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/lockdep.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/signal.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/string_helpers.h>
#include <linux/uaccess.h>
#include <asm/machdep.h>
#include <asm/rtas-work-area.h>
#include <asm/rtas.h>
#include <uapi/asm/papr-indices.h>
#include "papr-rtas-common.h"

/*
 * Function-specific return values for ibm,set-dynamic-indicator and
 * ibm,get-dynamic-sensor-state RTAS calls.
 * PAPR+ v2.13 7.3.18 and 7.3.19.
 */
#define RTAS_IBM_DYNAMIC_INDICE_NO_INDICATOR    -3

/**
 * struct rtas_get_indices_params - Parameters (in and out) for
 *                                      ibm,get-indices.
 * @is_sensor:  In: Caller-provided whether sensor or indicator.
 * @indice_type:In: Caller-provided indice (sensor or indicator) token
 * @work_area:  In: Caller-provided work area buffer for results.
 * @next:       In: Sequence number. Out: Next sequence number.
 * @status:     Out: RTAS call status.
 */
struct rtas_get_indices_params {
        u8 is_sensor;
        u32 indice_type;
        struct rtas_work_area *work_area;
        u32 next;
        s32 status;
};

/*
 * rtas_ibm_get_indices() - Call ibm,get-indices to fill a work area buffer.
 * @params: See &struct rtas_ibm_get_indices_params.
 *
 * Calls ibm,get-indices until it errors or successfully deposits data
 * into the supplied work area. Handles RTAS retry statuses. Maps RTAS
 * error statuses to reasonable errno values.
 *
 * The caller is expected to invoke rtas_ibm_get_indices() multiple times
 * to retrieve all indices data for the provided indice type. Only one
 * sequence should be in progress at any time; starting a new sequence
 * will disrupt any sequence already in progress. Serialization of
 * indices retrieval sequences is the responsibility of the caller.
 *
 * The caller should inspect @params.status to determine whether more
 * calls are needed to complete the sequence.
 *
 * Context: May sleep.
 * Return: -ve on error, 0 otherwise.
 */
static int rtas_ibm_get_indices(struct rtas_get_indices_params *params)
{
        struct rtas_work_area *work_area = params->work_area;
        const s32 token = rtas_function_token(RTAS_FN_IBM_GET_INDICES);
        u32 rets;
        s32 fwrc;
        int ret;

        if (token == RTAS_UNKNOWN_SERVICE)
                return -ENOENT;

        lockdep_assert_held(&rtas_ibm_get_indices_lock);

        do {
                fwrc = rtas_call(token, 5, 2, &rets, params->is_sensor,
                                params->indice_type,
                                rtas_work_area_phys(work_area),
                                rtas_work_area_size(work_area),
                                params->next);
        } while (rtas_busy_delay(fwrc));

        switch (fwrc) {
        case RTAS_HARDWARE_ERROR:
                ret = -EIO;
                break;
        case RTAS_INVALID_PARAMETER: /* Indicator type is not supported */
                ret = -EINVAL;
                break;
        case RTAS_SEQ_START_OVER:
                ret = -EAGAIN;
                pr_info_ratelimited("Indices changed during retrieval, retrying\n");
                params->next = 1;
                break;
        case RTAS_SEQ_MORE_DATA:
                params->next = rets;
                ret = 0;
                break;
        case RTAS_SEQ_COMPLETE:
                params->next = 0;
                ret = 0;
                break;
        default:
                ret = -EIO;
                pr_err_ratelimited("unexpected ibm,get-indices status %d\n", fwrc);
                break;
        }

        params->status = fwrc;
        return ret;
}

/*
 * Internal indices sequence APIs. A sequence is a series of calls to
 * ibm,get-indices for a given location code. The sequence ends when
 * an error is encountered or all indices for the input has been
 * returned.
 */

/*
 * indices_sequence_begin() - Begin a indices retrieval sequence.
 *
 * Context: May sleep.
 */
static void indices_sequence_begin(struct papr_rtas_sequence *seq)
{
        struct rtas_get_indices_params  *param;

        param = (struct rtas_get_indices_params *)seq->params;
        /*
         * We could allocate the work area before acquiring the
         * function lock, but that would allow concurrent requests to
         * exhaust the limited work area pool for no benefit. So
         * allocate the work area under the lock.
         */
        mutex_lock(&rtas_ibm_get_indices_lock);
        param->work_area = rtas_work_area_alloc(RTAS_GET_INDICES_BUF_SIZE);
        param->next = 1;
        param->status = 0;
}

/*
 * indices_sequence_end() - Finalize a indices retrieval sequence.
 *
 * Releases resources obtained by indices_sequence_begin().
 */
static void indices_sequence_end(struct papr_rtas_sequence *seq)
{
        struct rtas_get_indices_params *param;

        param =  (struct rtas_get_indices_params *)seq->params;
        rtas_work_area_free(param->work_area);
        mutex_unlock(&rtas_ibm_get_indices_lock);
}

/*
 * Work function to be passed to papr_rtas_blob_generate().
 *
 * ibm,get-indices RTAS call fills the work area with the certain
 * format but does not return the bytes written in the buffer. So
 * instead of kernel parsing this work area to determine the buffer
 * length, copy the complete work area (RTAS_GET_INDICES_BUF_SIZE)
 * to the blob and let the user space to obtain the data.
 * Means RTAS_GET_INDICES_BUF_SIZE data will be returned for each
 * read().
 */

static const char *indices_sequence_fill_work_area(struct papr_rtas_sequence *seq,
                                                size_t *len)
{
        struct rtas_get_indices_params *p;
        bool init_state;

        p = (struct rtas_get_indices_params *)seq->params;
        init_state = (p->next == 1) ? true : false;

        if (papr_rtas_sequence_should_stop(seq, p->status, init_state))
                return NULL;
        if (papr_rtas_sequence_set_err(seq, rtas_ibm_get_indices(p)))
                return NULL;

        *len = RTAS_GET_INDICES_BUF_SIZE;
        return rtas_work_area_raw_buf(p->work_area);
}

/*
 * papr_indices_handle_read - returns indices blob data to the user space
 *
 * ibm,get-indices RTAS call fills the work area with the certian
 * format but does not return the bytes written in the buffer and
 * copied RTAS_GET_INDICES_BUF_SIZE data to the blob for each RTAS
 * call. So send RTAS_GET_INDICES_BUF_SIZE buffer to the user space
 * for each read().
 */
static ssize_t papr_indices_handle_read(struct file *file,
                char __user *buf, size_t size, loff_t *off)
{
        const struct papr_rtas_blob *blob = file->private_data;

        /* we should not instantiate a handle without any data attached. */
        if (!papr_rtas_blob_has_data(blob)) {
                pr_err_once("handle without data\n");
                return -EIO;
        }

        if (size < RTAS_GET_INDICES_BUF_SIZE) {
                pr_err_once("Invalid buffer length %ld, expect %d\n",
                                size, RTAS_GET_INDICES_BUF_SIZE);
                return -EINVAL;
        } else if (size > RTAS_GET_INDICES_BUF_SIZE)
                size = RTAS_GET_INDICES_BUF_SIZE;

        return simple_read_from_buffer(buf, size, off, blob->data, blob->len);
}

static const struct file_operations papr_indices_handle_ops = {
        .read = papr_indices_handle_read,
        .llseek = papr_rtas_common_handle_seek,
        .release = papr_rtas_common_handle_release,
};

/*
 * papr_indices_create_handle() - Create a fd-based handle for reading
 *                                indices data
 * @ubuf: Input parameters to RTAS call such as whether sensor or indicator
 *        and indice type in user memory
 *
 * Handler for PAPR_INDICES_IOC_GET ioctl command. Validates @ubuf
 * and instantiates an immutable indices "blob" for it. The blob is
 * attached to a file descriptor for reading by user space. The memory
 * backing the blob is freed when the file is released.
 *
 * The entire requested indices is retrieved by this call and all
 * necessary RTAS interactions are performed before returning the fd
 * to user space. This keeps the read handler simple and ensures that
 * the kernel can prevent interleaving of ibm,get-indices call sequences.
 *
 * Return: The installed fd number if successful, -ve errno otherwise.
 */
static long papr_indices_create_handle(struct papr_indices_io_block __user *ubuf)
{
        struct papr_rtas_sequence seq = {};
        struct rtas_get_indices_params params = {};
        int fd;

        if (get_user(params.is_sensor, &ubuf->indices.is_sensor))
                return -EFAULT;

        if (get_user(params.indice_type, &ubuf->indices.indice_type))
                return -EFAULT;

        seq = (struct papr_rtas_sequence) {
                .begin = indices_sequence_begin,
                .end = indices_sequence_end,
                .work = indices_sequence_fill_work_area,
        };

        seq.params = &params;
        fd = papr_rtas_setup_file_interface(&seq,
                        &papr_indices_handle_ops, "[papr-indices]");

        return fd;
}

/*
 * Create work area with the input parameters. This function is used
 * for both ibm,set-dynamic-indicator and ibm,get-dynamic-sensor-state
 * RTAS Calls.
 */
static struct rtas_work_area *
papr_dynamic_indice_buf_from_user(struct papr_indices_io_block __user *ubuf,
                                struct papr_indices_io_block *kbuf)
{
        struct rtas_work_area *work_area;
        u32 length;
        __be32 len_be;

        if (copy_from_user(kbuf, ubuf, sizeof(*kbuf)))
                return ERR_PTR(-EFAULT);


        if (!string_is_terminated(kbuf->dynamic_param.location_code_str,
                        ARRAY_SIZE(kbuf->dynamic_param.location_code_str)))
                return ERR_PTR(-EINVAL);

        /*
         * The input data in the work area should be as follows:
         * - 32-bit integer length of the location code string,
         *   including NULL.
         * - Location code string, NULL terminated, identifying the
         *   token (sensor or indicator).
         * PAPR 2.13 - R1–7.3.18–5 ibm,set-dynamic-indicator
         *           - R1–7.3.19–5 ibm,get-dynamic-sensor-state
         */
        /*
         * Length that user space passed should also include NULL
         * terminator.
         */
        length = strlen(kbuf->dynamic_param.location_code_str) + 1;
        if (length > LOC_CODE_SIZE)
                return ERR_PTR(-EINVAL);

        len_be = cpu_to_be32(length);

        work_area = rtas_work_area_alloc(LOC_CODE_SIZE + sizeof(u32));
        memcpy(rtas_work_area_raw_buf(work_area), &len_be, sizeof(u32));
        memcpy((rtas_work_area_raw_buf(work_area) + sizeof(u32)),
                        &kbuf->dynamic_param.location_code_str, length);

        return work_area;
}

/**
 * papr_dynamic_indicator_ioc_set - ibm,set-dynamic-indicator RTAS Call
 * PAPR 2.13 7.3.18
 *
 * @ubuf: Input parameters to RTAS call such as indicator token and
 *        new state.
 *
 * Returns success or -errno.
 */
static long papr_dynamic_indicator_ioc_set(struct papr_indices_io_block __user *ubuf)
{
        struct papr_indices_io_block kbuf;
        struct rtas_work_area *work_area;
        s32 fwrc, token, ret;

        token = rtas_function_token(RTAS_FN_IBM_SET_DYNAMIC_INDICATOR);
        if (token == RTAS_UNKNOWN_SERVICE)
                return -ENOENT;

        mutex_lock(&rtas_ibm_set_dynamic_indicator_lock);
        work_area = papr_dynamic_indice_buf_from_user(ubuf, &kbuf);
        if (IS_ERR(work_area)) {
                ret = PTR_ERR(work_area);
                goto out;
        }

        do {
                fwrc = rtas_call(token, 3, 1, NULL,
                                kbuf.dynamic_param.token,
                                kbuf.dynamic_param.state,
                                rtas_work_area_phys(work_area));
        } while (rtas_busy_delay(fwrc));

        rtas_work_area_free(work_area);

        switch (fwrc) {
        case RTAS_SUCCESS:
                ret = 0;
                break;
        case RTAS_IBM_DYNAMIC_INDICE_NO_INDICATOR:      /* No such indicator */
                ret = -EOPNOTSUPP;
                break;
        default:
                pr_err("unexpected ibm,set-dynamic-indicator result %d\n",
                        fwrc);
                fallthrough;
        case RTAS_HARDWARE_ERROR:       /* Hardware/platform error */
                ret = -EIO;
                break;
        }

out:
        mutex_unlock(&rtas_ibm_set_dynamic_indicator_lock);
        return ret;
}

/**
 * papr_dynamic_sensor_ioc_get - ibm,get-dynamic-sensor-state RTAS Call
 * PAPR 2.13 7.3.19
 *
 * @ubuf: Input parameters to RTAS call such as sensor token
 *        Copies the state in user space buffer.
 *
 *
 * Returns success or -errno.
 */

static long papr_dynamic_sensor_ioc_get(struct papr_indices_io_block __user *ubuf)
{
        struct papr_indices_io_block kbuf;
        struct rtas_work_area *work_area;
        s32 fwrc, token, ret;
        u32 rets;

        token = rtas_function_token(RTAS_FN_IBM_GET_DYNAMIC_SENSOR_STATE);
        if (token == RTAS_UNKNOWN_SERVICE)
                return -ENOENT;

        mutex_lock(&rtas_ibm_get_dynamic_sensor_state_lock);
        work_area = papr_dynamic_indice_buf_from_user(ubuf, &kbuf);
        if (IS_ERR(work_area)) {
                ret = PTR_ERR(work_area);
                goto out;
        }

        do {
                fwrc = rtas_call(token, 2, 2, &rets,
                                kbuf.dynamic_param.token,
                                rtas_work_area_phys(work_area));
        } while (rtas_busy_delay(fwrc));

        rtas_work_area_free(work_area);

        switch (fwrc) {
        case RTAS_SUCCESS:
                if (put_user(rets, &ubuf->dynamic_param.state))
                        ret = -EFAULT;
                else
                        ret = 0;
                break;
        case RTAS_IBM_DYNAMIC_INDICE_NO_INDICATOR:      /* No such indicator */
                ret = -EOPNOTSUPP;
                break;
        default:
                pr_err("unexpected ibm,get-dynamic-sensor result %d\n",
                                fwrc);
                fallthrough;
        case RTAS_HARDWARE_ERROR:       /* Hardware/platform error */
                ret = -EIO;
                break;
        }

out:
        mutex_unlock(&rtas_ibm_get_dynamic_sensor_state_lock);
        return ret;
}

/*
 * Top-level ioctl handler for /dev/papr-indices.
 */
static long papr_indices_dev_ioctl(struct file *filp, unsigned int ioctl,
                                unsigned long arg)
{
        void __user *argp = (__force void __user *)arg;
        long ret;

        switch (ioctl) {
        case PAPR_INDICES_IOC_GET:
                ret = papr_indices_create_handle(argp);
                break;
        case PAPR_DYNAMIC_SENSOR_IOC_GET:
                ret = papr_dynamic_sensor_ioc_get(argp);
                break;
        case PAPR_DYNAMIC_INDICATOR_IOC_SET:
                if (filp->f_mode & FMODE_WRITE)
                        ret = papr_dynamic_indicator_ioc_set(argp);
                else
                        ret = -EBADF;
                break;
        default:
                ret = -ENOIOCTLCMD;
                break;
        }

        return ret;
}

static const struct file_operations papr_indices_ops = {
        .unlocked_ioctl = papr_indices_dev_ioctl,
};

static struct miscdevice papr_indices_dev = {
        .minor = MISC_DYNAMIC_MINOR,
        .name = "papr-indices",
        .fops = &papr_indices_ops,
};

static __init int papr_indices_init(void)
{
        if (!rtas_function_implemented(RTAS_FN_IBM_GET_INDICES))
                return -ENODEV;

        if (!rtas_function_implemented(RTAS_FN_IBM_SET_DYNAMIC_INDICATOR))
                return -ENODEV;

        if (!rtas_function_implemented(RTAS_FN_IBM_GET_DYNAMIC_SENSOR_STATE))
                return -ENODEV;

        return misc_register(&papr_indices_dev);
}
machine_device_initcall(pseries, papr_indices_init);