mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

The RTAS call ibm,get-dynamic-sensor-state is used to get the sensor state identified by the location code and the sensor token. The librtas library provides an API rtas_get_dynamic_sensor() which uses /dev/mem access for work area allocation but is restricted under system lockdown. This patch provides an interface with new ioctl PAPR_DYNAMIC_SENSOR_IOC_GET to the papr-indices character driver which executes this HCALL and copies the sensor state in the user specified ioctl buffer. Refer PAPR 7.3.19 ibm,get-dynamic-sensor-state for more information on this RTAS call. - User input parameters to the RTAS call: location code string and the sensor token Expose these interfaces to user space with a /dev/papr-indices character device using the following programming model: int fd = open("/dev/papr-indices", O_RDWR); int ret = ioctl(fd, PAPR_DYNAMIC_SENSOR_IOC_GET, struct papr_indices_io_block) - The user space specifies input parameters in papr_indices_io_block struct - Returned state for the specified sensor is copied to papr_indices_io_block.dynamic_param.state Signed-off-by: Haren Myneni <haren@linux.ibm.com> Tested-by: Sathvika Vasireddy <sv@linux.ibm.com> Signed-off-by: Madhavan Srinivasan <maddy@linux.ibm.com> Link: https://patch.msgid.link/20250416225743.596462-6-haren@linux.ibm.com
488 lines
13 KiB
C
488 lines
13 KiB
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 = ¶ms;
|
||
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);
|